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: callingredirect() 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 onIResponse (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:
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 default200, 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.
200:
Returning errors
Useexception() 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.
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:
write(chunk), close(), and a signal (AbortSignal) that aborts when the client disconnects; check it to stop work early:
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.
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.
setCookie(name, value, options?). Options cover path, domain, expires, maxAge, secure, httpOnly, and sameSite ("Strict" | "Lax" | "None").
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
Returncontext.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.
Related
- 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.