Skip to main content
Every controller returns its reply through context.response, an IResponse object the framework hands you on the request context. You never construct a Response by hand. You call builder methods (json, exception, notFound, redirect) that set the status, body, and headers, then the framework calls get() for you and sends a standard Web API Response to the client. Each builder returns the response instance, so calls chain, and the last write wins.

What the response object does

The four common builders, json(), exception(), notFound(), and redirect(), each return IResponse, so you can compose status, body, and headers in a single expression. JSON responses are wrapped in a consistent envelope (success, status, message, data, error flags, and environment info) so every client parses replies the same way. Underneath, get() produces a native Response whose body, status, and headers are exactly what a browser or fetch client expects. HttpResponse<Data> is generic over your payload type, so json(data) is checked against the shape you declared. context.response.header exposes the full header API for setting headers, attaching cookies, and managing caching, and those ride along with whatever body you set. For WebSocket responses, a public done flag tracks completion without touching the HTTP body.

How it works

You mutate the response object during the request, and the framework reads it afterward. Each builder method resets the others: calling redirect() clears any JSON body and sets the Location header, and calling json() clears the redirect URL. So the response always reflects the last builder you called.

The response builder

These are the methods on IResponse (implemented by HttpResponse). The builder methods return the response instance for chaining; the inspector methods read its current state.

The JSON envelope

json() does not send your data verbatim. It wraps it in a consistent envelope so every client reads success and error the same way. get(env) produces this body:
The success, isClientError, and isServerError flags are derived from the status code automatically. A redirect() or stream()/sse() response skips this envelope: a redirect sends an empty body with the Location header, and a stream sends its raw bytes.

Returning JSON

Return data with the default 200, or pass a status code as the second argument, like 201 when you create a resource. The controller receives context and returns the response object.
A plain read returns the default 200:

Returning errors

Use exception() for failures. The message is surfaced to the client; config lets you set the status, a machine-readable key, and extra data. The status defaults to 500.
For richer, typed error handling, throw a domain exception and let the framework’s exception layer format the response. See exceptions.

Returning a 404

notFound() is a dedicated helper for missing resources. The status defaults to 404 and the key defaults to "NOT_FOUND".

Redirecting

redirect() sets the Location header and an empty body. The status defaults to 302 (Found); pass another redirect code for permanent moves or other semantics.

Streaming a response

stream() sends a body incrementally instead of buffering it. It accepts a ReadableStream, any async iterable of Uint8Array or string chunks, or a producer function that receives a writer and pushes chunks over time. config sets the status (default 200) and contentType (default application/octet-stream). A streamed response skips the JSON envelope and sends raw bytes. Pass an async iterable to stream values as they are produced:
Or pass a producer function. The writer exposes write(chunk), close(), and a signal (AbortSignal) that aborts when the client disconnects; check it to stop work early:
The stream closes automatically when the producer resolves, so writer.close() is optional. Call it to end the stream before the producer returns.

Server-Sent Events

sse() streams Server-Sent Events to the client. It takes a producer function and automatically sets the text/event-stream, Cache-Control: no-cache, and Connection: keep-alive headers. config accepts a status (default 200). The writer’s send() accepts a string or an event object, { data, event?, id?, retry? }. Object or array data is JSON-encoded; comment() sends a keep-alive comment line, and signal aborts when the client disconnects.
A plain send("message") emits a bare data: frame, while send({ data, event, id, retry }) sets the optional event, id, and retry fields. As with stream(), the connection closes when the producer resolves.

Headers and cookies

context.response.header is the full header API. Set headers before returning, and they travel with whatever body you set.
Attach cookies with setCookie(name, value, options?). Options cover path, domain, expires, maxAge, secure, httpOnly, and sameSite ("Strict" | "Lax" | "None").
Use setCookies([...]) to set several at once, and removeCookie(name, options?) to expire one (it sends the cookie with an expired date and Max-Age=0).

Building responses well

Return context.response from a controller’s action and let the framework call get() and send the native Response; don’t build a Response yourself. Pick the builder that matches the outcome: json for data, exception for errors, notFound for missing resources, redirect for location changes. Each clears the others, so the last call is the one that ships. Set a status that fits the outcome (201 on create, 400/422 for bad input, 404 for missing), since clients and the envelope’s success/error flags depend on it. A stable machine-readable key on errors lets clients branch on error type without parsing the message. Configure headers and cookies on context.response.header before the final builder call, because they’re read when the response is resolved. For anything beyond a one-off error, throw a domain exception and let the exception layer shape the response consistently.
  • Controllers: where you build and return the response.
  • Exceptions: typed errors that the framework turns into error responses.
  • Routing: how a request reaches the controller that produces the response.