Skip to content
Flare 0.1.x is pre-release. Expect breaking changes before 1.0 — see the changelog.

Response

This page documents what Flare sends back to the client: the types handlers and middleware may return, how those values become bytes on the wire, and how contract response schemas shape JSON.

For matching semantics (404/405, HEAD, OPTIONS, CORS), see Routing reference. For reading request data, see Request.

SymbolKindRole
FlareResponseclassFramework response with status, headers, and body
HandlerResulttypeUnion of values a route handler may return
ResponseLiketypeOutbound response the runtime writes (FlareResponse or web Response)
MiddlewareOverridetypeMiddleware hook return: any HandlerResult except null or undefined, or void to pass through
ResponseHeaderstypePlain header map on FlareResponse.headers (Record<string, string>)

Middleware hooks use MiddlewareOverride. Error handlers return ResponseLike | void only (not plain objects). See Middleware and HTTP errors.

Import the types and class from @flare-ts/core:

import { FlareResponse } from "@flare-ts/core";
import type {
HandlerResult,
MiddlewareOverride,
ResponseHeaders,
ResponseLike,
} from "@flare-ts/core";

Union of all permitted handler return values:

type HandlerResult =
| FlareResponse
| Response
| AsyncIterable<unknown>
| object
| null
| undefined;

Handlers may return these synchronously or inside a Promise. AsyncIterable<unknown> is its own union member (not part of object). The object arm covers plain objects, arrays, branded model() instances, and other object values. Error instances also match object at runtime but are re-thrown instead of serialized (see Return values).

Do not return null from middleware overrides; the framework rejects it the same way as a missing handler return. Return void or undefined to pass through.

type ResponseLike = Response | FlareResponse;

Plain objects, arrays, branded models, and streams in a HandlerResult become a FlareResponse before the runtime writes the response. Web Response values pass through unchanged for fetch-style interop.

These rules apply after the handler and middleware hooks (after, finally) resolve, before the runtime writes bytes.

ReturnHandlingClient outcome
FlareResponsePass through. JSON still on jsonBody is serialized with the route’s per-status serializer for that status (when declared), then moved to body.Your status, headers, and body (or stream).
Response (web)Pass through unchanged.Whatever the Response carries. Set-Cookie from ctx.cookies is merged in by the adapter when present.
Plain object or arrayWrapped as new FlareResponse(200, value) and serialized.200 JSON. Uses the route’s 200 response serializer when declared; otherwise JSON.stringify.
Branded model() instanceSerialized with the route’s 200 serializer when declared, otherwise the model’s compiled serializer, otherwise JSON.stringify. Wrapped as 200 JSON.200 JSON.
Other object (custom class, not a branded model)Same outcome as plain objects: 200 JSON via per-status serializer or JSON.stringify.200 JSON.
AsyncIterableWrapped in a streaming FlareResponse(200, …). Each chunk becomes Uint8Array (Uint8Array as-is, string UTF-8-encoded, anything else JSON.stringify then encoded).200 chunked stream.
Returned Error instanceRe-thrown (not serialized as 200 JSON).Not handled by host.http.error. Production adapters return 500 { error: "Internal Server Error" }; app.fetch() rejects in test mode. Throw errors to reach HTTP error handlers.
null / undefinedThrows "Handler returned null/undefined. Did you forget to return a response?"Not handled by host.http.error. Production adapters return 500 JSON; fix the handler return. See Failure modes → Runtime throws.
Primitives, functions, symbolsThrows "Handler returned an unsupported type. Use a response helper or return a FlareResponse."Same outcome as null / undefined: not handled by host.http.error; return a FlareResponse, web Response, or serializable object instead.

Common patterns:

import { FlareResponse } from "@flare-ts/core";
host.http.get("/ping", () => new FlareResponse(200, { ok: true }));
host.http.get("/version", () => ({ version: "1" }));

A plain object return is always 200. For 404, 400, or other statuses, return new FlareResponse(status, body), a controller helper such as this.notFound(...), or another value that sets the status explicitly.

Values that are already ResponseLike pass through without object wrapping (for example a FlareResponse from a before() short-circuit or a controller helper). Plain objects and other non-ResponseLike results, including middleware overrides, follow the same rules as handler returns.

FlareResponse is the framework response type exported from @flare-ts/core.

MemberTypeMeaning
statusnumberHTTP status code.
headersResponseHeadersOutbound headers (plain record, not the web Headers API).
bodyUint8Array | string | nullMaterialized body after serialization, or null for empty responses.
jsonBodyJsonValue | nullJSON object (or scalar) held before the per-status serializer runs; null once finalized into body.
bodyStreamAsyncIterable<Uint8Array> | nullChunked body for streaming responses; null for buffered bodies.

Cookies are not a field on FlareResponse. Set outbound cookies with ctx.cookies.set(...) on the request context; the runtime drains them into Set-Cookie when the response is written. See Cookies.

new FlareResponse(status);
new FlareResponse(status, body, init?);

init is { headers?: ResponseHeaders }. Header keys from init are merged into headers (body-derived headers such as Content-Type and Content-Length are set by the constructor based on body kind).

Body overloads (second argument):

Body argumentbody / bodyStreamHeaders set by constructor
Omitted, null, or undefinedEmpty (body and bodyStream are null)Only headers from init.
JSON value (JsonValue: object, array, number, boolean, or null)Stored on jsonBody until serialized; then body is the JSON stringContent-Type: application/json, Content-Length filled when serialized
stringbody as UTF-8 text (not jsonBody)Content-Type: text/plain, Content-Length
Uint8ArraybodyContent-Length
AsyncIterable<Uint8Array>bodyStreamNo Content-Length (chunked)

JSON objects and arrays keep their object form on jsonBody until the per-status serializer runs. Strings passed as the second argument are sent as text/plain, not JSON. Use a JSON object (for example { message: "ok" }) or a controller helper when you want application/json.

import { FlareResponse } from "@flare-ts/core";
host.http.get("/me", () => {
const res = new FlareResponse(200, { id: "u1" });
res.headers["cache-control"] = "no-store";
return res;
});
new FlareResponse(200, { ok: true }, {
headers: { "cache-control": "no-store" },
});
new FlareResponse(204); // no body
new FlareResponse(200, new Uint8Array([0x89, 0x50])); // binary

Streaming from a handler can also return an AsyncIterable directly (see Return values); constructing new FlareResponse(200, asyncIterable) is equivalent.

ControllerBase and MiddlewareBase expose protected helpers that return ResponseLike (each wraps new FlareResponse(...)):

HelperStatusNotes
ok(body)200JSON or text (JsonValue)
created(body)201JSON or text
noContent()204Empty body
redirect(location, options?)302 defaultRedirectOptions: permanent → 301/308; preserveMethod → 307/308
badRequest(body)400
unauthorized(body)401
forbidden(body)403
notFound(body)404
tooManyRequests(body)429
error(body)500HTTP response helper; not host.http.error

Controllers include the full set above; middleware includes the error-status helpers (400–429, 500) but not ok, created, noContent, or redirect. See Routes for controller usage.

Assign on FlareResponse.headers with bracket notation, or pass headers in the constructor init:

host.http.get("/download", () => {
const res = new FlareResponse(200, { ok: true });
res.headers["x-content-type-options"] = "nosniff";
return res;
});

For CORS response headers on normal traffic, Flare applies the compiled CORS policy after the handler returns. Preflight OPTIONS responses are synthesized separately. See Routing reference.

When request id headers are enabled, the runtime adds x-request-id on the way out.

Outbound cookies are set on ctx.cookies (FlareHttpContext), not on FlareResponse:

host.http.post("/login", (ctx) => {
ctx.cookies.set("session", token, { httpOnly: true, sameSite: "Lax" });
return { ok: true };
});

ctx.cookies.get(name) and ctx.cookies.getAll() read the inbound Cookie header (lazy, cached). ctx.cookies.set(name, value, options?) accumulates Set-Cookie values; the adapter appends them when writing the final response (including when you return a web Response). sameSite: "None" requires secure: true at compile time and throws at runtime otherwise.

When a route declares contract.response, Flare compiles a per-status serializer. On the way out, fields not in the schema for that status are dropped from JSON.

import { schema, str } from "@flare-ts/lib/schema";
const me = {
response: {
200: schema({ id: str, name: str }),
},
};
host.http.get("/me", { contract: me }, () => {
return { id: "u1", name: "Alice", password: "secret" };
// password is omitted from the serialized 200 response
});

For FlareResponse with a JSON body, the serializer for response.status runs (not always 200). Plain object and model returns always become 200, so only the 200 schema applies to those shapes.

See Contracts for response descriptor shape, per-status error schemas, and validation failure bodies.

host.http.error handlers return ResponseLike | void, not arbitrary HandlerResult values. Return new FlareResponse(...), a web Response, or void to defer to the next handler. Plain objects defer like void (error handlers must return ResponseLike). See HTTP errors.

Some responses never pass through your handler or host.http.error():

  • 404 when nothing matches (plain "Not Found" body)
  • 405 when the path matches but the method does not
  • Contract input 400 responses (route, query, or body validation)
  • 503 when the host is not built

These are built directly in the HTTP arc. Custom error handlers do not run for contract validation failures.

See Failure modes for the full catalog and shapes.

  • Contracts: response descriptors and validation failures
  • Middleware: short-circuit, after transforms, and finally
  • HTTP errors: error handler return types
  • Failure modes: 404/405/400/413/503 and error mapping behavior