context.response, an IResponse object the framework hands you on the request context. You never construct a Response by hand: you call fluent 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.
Why this object
- Fluent and chainable.
json(),exception(),notFound(), andredirect()each returnIResponse, so you compose status, body, and headers in one expression. - Structured JSON envelope. JSON responses are wrapped in a consistent shape —
success,status,message,data, error flags, and environment info — so every client parses replies the same way. - Web standards out. Internally
get()produces a nativeResponse; the body, status, and headers are exactly what a browser orfetchclient expects. - Type-safe data.
HttpResponse<Data>is generic over your payload type, sojson(data)is checked against the shape you declared. - Headers and cookies built in.
context.response.headerexposes the full header API — set headers, attach cookies, manage caching — and they ride along with whatever body you set. - Socket-aware. A public
doneflag tracks completion for WebSocket responses without changing the HTTP body.
How it works
You mutate the response object during the request; the framework reads it afterward. Each builder method resets the others — callingredirect() clears any JSON body and sets the Location header, calling json() clears the redirect URL — so the response always reflects the last builder you called.
| Stage | What happens |
|---|---|
| In the controller | You call a builder on context.response (e.g. context.response.json(data, 201)), optionally setting headers and cookies on context.response.header. |
| Last write wins | Each builder clears the state set by the others, so the final call determines the body, status, and Content-Type. |
| Framework resolves | The framework calls get(env) on your response, which serializes the JSON envelope (or builds the redirect/stream) into a native Response. |
| Sent to client | The Response — status, headers, body — is returned over the wire. |
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.
| Method | Purpose |
|---|---|
json(data, status?) | Set a JSON body. status defaults to 200. Wraps data in the standard envelope. |
exception(message, config?) | Build an error response. status defaults to 500. config accepts key, data, and status. |
notFound(message, config?) | Build a 404 response. status defaults to 404, key defaults to "NOT_FOUND". config accepts key, data, and status. |
redirect(url, status?) | Set a Location header and an empty body. status defaults to 302 (Found). Accepts a string or URL. |
stream(body, config?) | Stream a ReadableStream, async iterable, or producer function. config accepts status and contentType. |
sse(producer, config?) | Stream Server-Sent Events from a producer function. Sets the text/event-stream headers. |
header | The IHeader instance — set response headers, cookies, and cache directives. |
done | A public boolean flag, default false, for tracking WebSocket response completion. |
getData() | Read the currently set data payload, or null. |
getStatus() | Read the current status code. |
isStream() | true if a streaming/SSE body is set. |
get(env?) | Build and return the native Web API Response. The framework calls this for you. |
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 — 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 — 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).
Best practices
- Return the response object. A controller’s
actionreturnscontext.response; the framework callsget()and sends the nativeResponse— don’t build aResponseyourself. - Pick the right builder.
jsonfor data,exceptionfor errors,notFoundfor missing resources,redirectfor location changes. Each clears the others, so the last call wins. - Set the status that fits.
201on create,400/422for bad input,404for missing — clients and the JSON envelope’ssuccess/error flags depend on it. - Add a
keyto errors. A stable machine-readablekeylets clients branch on error type without parsing the message. - Set headers before returning. Headers and cookies on
context.response.headerare read when the response is resolved, so configure them before the final builder call. - Prefer typed exceptions for control flow. 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.