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

Request

Every HTTP handler and middleware hook receives a FlareHttpContext (ctx in inline routes; this.ctx on controller classes). The inbound message is ctx.req, a FlareRequest exported from @flare-ts/core.

For route matching (404/405, HEAD, OPTIONS, CORS), see Routing reference. For handler return values, see Response. For contract descriptors and validation timing, see Contracts.

FlareRequest is exported from @flare-ts/core as a read-mostly wrapper around what arrived on the wire. Pipeline concerns (parsed contract values, request state, cookies) live on FlareHttpContext, not on the request object. CookieOptions is the type for ctx.cookies.set() on the context, not a field on FlareRequest.

FieldTypeMeaning
methodstringHTTP verb as received (for example "GET", "POST")
urlstringFull request URL string
pathstringURL path without the query string (derived from url)
headersHeadersRequest headers (standard Web Headers)
rawRouteParamsRecord<string, string>Decoded :segment and *rest values from the matched route; {} when the route has no params
rawQueryParamsURLSearchParamsParsed query string from url
requestIdstringFramework-assigned id for this request (also sent as X-Request-Id on responses when request id headers are enabled)
rawBodyArrayBuffer | nullBytes already buffered by buffer(), text(), or json(), or null before any of those run
signalAbortSignalClient disconnect / abort signal from the runtime adapter
startTimenumber | undefinedPresent when Config host.requestTiming is enabled: a Date.now() snapshot captured when the adapter created the request
nativeRequestunknownAdapter-specific raw request object (for example Node IncomingMessage, or Workers Request)

nativeRequest is adapter-specific: on the Node adapter it is the underlying Node IncomingMessage, and on the Cloudflare/fetch adapter it is a platform Request. The exact type depends on the adapter you pass to new FlareHost(...).

buffer(), text(), and json() return promises. stream() is synchronous and returns an AsyncIterable of chunks.

The three buffered readers share one in-flight read: the first call buffers the body into rawBody; later calls reuse those bytes without reading the wire again.

MethodReturnsBehavior
buffer(maxBytes?)Promise<ArrayBuffer | null>Reads the full body into memory. Resolves null when there is no body or the body is empty. The optional maxBytes overrides the route/host limit for this call only.
text()Promise<string | null>UTF-8 text of the buffered body; null when the body is missing or empty.
json()Promise<unknown | null>Parses JSON from the buffered body; null when the body is missing or empty. Rejects with SyntaxError ("Invalid JSON body") when bytes are not valid JSON.
stream()AsyncIterable<Uint8Array>Iterates the inbound body without going through buffer(). Throws if buffer(), text(), or json() have started or finished reading the body.

Size limit. When a route matches, the effective cap is the route descriptor’s maxBodyBytes or host.maxBodyBytes (default 2 MiB). buffer() enforces that cap (and any per-call maxBytes you pass). Bodies over the limit throw FlareError with name ContentTooLarge, which the pipeline turns into 413 and { error: "ContentTooLarge", code: 413, detail: { maxBytes } }. See Failure modes.

Buffered vs streaming. Pick one strategy per request. buffer(), text(), and json() consume the inbound body stream. After any of them has started or finished (including an empty body), stream() throws:

[flare] stream() cannot be called after buffer(), text(), or json() have read the request body. Pick one read strategy per request.

Call stream() first when you need a streaming read; call buffer(), text(), or json() when you need the full body in memory.

ctx.req.signal is an AbortSignal from the runtime adapter.

If the client might disconnect while you read the body, read ctx.req.signal before calling buffer(), text(), or json(). While buffering, Flare checks the signal before and during chunk iteration. An abort rejects the promise with signal.reason when the runtime set one, otherwise new Error("Request aborted."). There is no separate Flare error type for disconnects.

stream() does not consult signal inside FlareRequest. When you stream manually, listen for abort on ctx.req.signal or handle errors from the iterable.

Without a contract (or for keys you did not declare), read the wire form:

host.http.get("/users/:id", (ctx) => {
const id = ctx.req.rawRouteParams["id"]; // string
const include = ctx.req.rawQueryParams.get("include"); // string | null
return { id, include };
});

rawRouteParams is filled as soon as a route matches, before before middleware runs. Values are decodeURIComponent of the matched segments. Catch-all routes (*name) store the rest of the path under that key.

rawQueryParams is always available from url; it is not validated until you declare a query contract.

When the route registers a contract, Flare validates route and query descriptors before before middleware. Coerced values are available through ctx.extract(descriptor) (same object identity as in route options or flareContract). Invalid route or query shapes short-circuit with 400 and the framework error bodies in Contracts → Validation failures.

import { int } from "@flare-ts/lib/schema";
const getUser = { route: { id: int } };
host.http.get("/users/:id", { contract: getUser }, (ctx) => {
const { route } = ctx.extract(getUser);
return { id: route.id }; // number
});
InputWithout contractWith contract
Path segmentsctx.req.rawRouteParams (string)ctx.extract(…).route (coerced primitives)
Query keysctx.req.rawQueryParams (URLSearchParams)ctx.extract(…).query (coerced primitives)
JSON bodyctx.req.buffer() / text() / json()ctx.extract(…).body after the pre-handler body stage

JSON body contracts are validated later: after before middleware, immediately before the handler. In before hooks you can rely on extract().route and extract().query, but not on a parsed JSON body yet. See Contracts for the stage diagram and error shapes.

Request headers are ctx.req.headers, a standard Headers instance:

host.http.get("/me", (ctx) => {
const auth = ctx.req.headers.get("authorization");
return { hasAuth: Boolean(auth) };
});

How Flare handles the body depends on the route descriptor:

Flare does not parse JSON for you. Use ctx.req.buffer(), text(), json(), or stream() as needed. Each buffered read respects the effective maxBodyBytes for that route.

In the handler, read the validated payload from ctx.extract(descriptor).body (typed to your schema). The pipeline buffers via ctx.req.buffer(), parses JSON, and validates in a pre-handler stage after before middleware. Validation failures return 400 with { error: "Invalid request body", details: … }.

With a JSON body contract, an empty or missing body becomes extract().body === null (not undefined).

Flare does not buffer JSON for stream body contracts. The inbound stream is available when the route matches, before before middleware runs. Iterate ctx.req.stream() or consume ctx.extract(descriptor).body; both reference the same stream iterable. Import the marker from @flare-ts/core and see Contracts → Streaming request bodies for examples.

  • Contracts: descriptors, ctx.extract, and validation timing
  • Middleware: before / after / finally and request-scoped scope
  • State: per-request tokens on ctx.state
  • Config: host.maxBodyBytes, host.requestTiming, host.requestIdHeader