Skip to content

Response Helpers

The thunder:http module provides typed response builders with a fluent API, an HTTP status map, and a generic auto-response adapter.

import {
HTTP,
JSONResponse,
TextResponse,
HTMLResponse,
BinaryResponse,
FileResponse,
BlobResponse,
StreamResponse,
RedirectResponse,
EmptyResponse,
ErrorResponse,
GenericResponse,
AutoResponse,
fromGenericResponse,
} from "thunder:http";

The thunder:http alias is resolved by Thunder CLI flows (thunder watch, thunder bundle, thunder test, thunder check).


HTTP exports named status codes as integer constants.

ConstantValueConstantValue
HTTP.Ok200HTTP.BadRequest400
HTTP.Created201HTTP.Unauthorized401
HTTP.Accepted202HTTP.Forbidden403
HTTP.NoContent204HTTP.NotFound404
HTTP.MovedPermanently301HTTP.MethodNotAllowed405
HTTP.Found302HTTP.Conflict409
HTTP.PermanentRedirect308HTTP.InternalServerError500
HTTP.Ok; // 200
HTTP.Created; // 201
HTTP.NoContent; // 204
HTTP.MethodNotAllowed; // 405
HTTP.InternalServerError; // 500

All builders return a ResponseDraft object that supports chaining. Call .toResponse() to produce a standard Response.

MethodDescription
.status(code)Set the HTTP status code
.header(key, value)Set a response header
.appendHeader(key, value)Append a value to a header (for multi-value headers)
.withHeaders(headers)Set multiple headers at once
.cookie(name, value, attributes?)Set a Set-Cookie header
.toResponse()Convert to a standard Response object
PropertyTypeDescription
statusCodenumberHTTP status code
headersHeadersResponse headers
bodyvariesResponse body
return JSONResponse({ message: "created" })
.status(HTTP.Created)
.header("x-request-id", "abc-123")
.cookie("session", "tok_xyz", { httpOnly: true, path: "/" })
.toResponse();

Direct property mutation is also supported:

const resp = JSONResponse({ ok: true });
resp.statusCode = HTTP.Ok;
resp.headers.set("x-env", "production");
return resp.toResponse();

Serializes a value as JSON with content-type: application/json.

return JSONResponse({ id: 42, name: "item" })
.status(HTTP.Created)
.toResponse();

Returns a plain text body with content-type: text/plain.

return TextResponse("pong")
.status(HTTP.Ok)
.toResponse();

Returns an HTML body with content-type: text/html.

return HTMLResponse("<h1>Hello, world</h1>")
.status(HTTP.Ok)
.toResponse();

Returns raw bytes with content-type: application/octet-stream.

const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
return BinaryResponse(bytes)
.status(HTTP.Ok)
.toResponse();

Returns bytes as a file download. Use .attachment(filename) to set the Content-Disposition header.

const data = new TextEncoder().encode("report content");
return FileResponse(data)
.attachment("report.txt")
.status(HTTP.Ok)
.toResponse();

Returns a Blob body. Use .filename(name, disposition) to configure download behavior.

const blob = new Blob(["hello"], { type: "text/plain" });
return BlobResponse(blob)
.filename("hello.txt", "attachment")
.toResponse();

Returns a ReadableStream body. Supports SSE and NDJSON helpers.

Plain stream:

const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("chunk-1\n"));
controller.enqueue(new TextEncoder().encode("chunk-2\n"));
controller.close();
},
});
return StreamResponse(stream).toResponse();

Server-Sent Events (SSE):

return StreamResponse(stream)
.sse()
.toResponse();

The .sse() helper sets content-type: text/event-stream and cache-control: no-cache.

Newline-delimited JSON (NDJSON):

return StreamResponse(stream)
.ndjson()
.toResponse();

The .ndjson() helper sets content-type: application/x-ndjson.

Returns a redirect response with a Location header.

return RedirectResponse("/new-path")
.status(HTTP.PermanentRedirect)
.toResponse();

Returns a response with no body.

return EmptyResponse()
.status(HTTP.NoContent)
.toResponse();

Returns a JSON error body with a code and optional detail object.

return ErrorResponse("invalid_payload", { expected: "{ name: string }" })
.status(HTTP.BadRequest)
.toResponse();

Produces:

{
"error": "invalid_payload",
"detail": { "expected": "{ name: string }" }
}

GenericResponse(value, init?) auto-detects the value type and returns a standard Response. Aliases: AutoResponse, fromGenericResponse.

export default async function handler(req: Request): Promise<Response> {
if (req.method === "GET") {
return GenericResponse({ ok: true }); // JSON
}
if (req.method === "POST") {
return GenericResponse("accepted", { status: HTTP.Accepted }); // text/plain
}
return GenericResponse(null); // 204 No Content
}

GenericResponse applies the following rules in order:

PriorityInput TypeResult
1ResponsePassthrough (returned as-is)
2ResponseDraftCalls .toResponse()
3Envelope { body, status?, headers? }Uses envelope metadata
4null or undefinedEmpty response (default 204)
5ReadableStreamStream response
6BlobBlob response
7ArrayBuffer or typed arrayBinary response
8stringText response (text/plain)
9number, boolean, bigintText response via String(value)
10Object or arrayJSON response (application/json)

Return an object with body, status, and headers fields to control the response metadata alongside the body:

return GenericResponse({
status: HTTP.Created,
headers: { "x-resource-id": "42" },
body: { id: 42, name: "item" },
});

When using export default { GET, POST, ... }, Thunder handles 405 Method Not Allowed with an Allow header automatically for unmatched methods.

import { HTTP, JSONResponse } from "thunder:http";
export default {
GET() {
return JSONResponse({ ok: true }).status(HTTP.Ok).toResponse();
},
POST(req: Request) {
return JSONResponse({ created: true }).status(HTTP.Created).toResponse();
},
};