Res

Response building: status codes, body serialization, streaming, and cookies.

A Res is what a handler shapes on its way to a native Response. It is created lazily on Context.res, so a handler that just returns a value never constructs one; reach for it when the response needs a status, headers, cookies, or a body form that a plain return value cannot express — a file, a redirect, or a stream.

Body serialization is inferred from the value's type at Res.toNativeResponse time, so an object becomes JSON, a typed array stays binary, and a stream passes through untouched — each with a matching Content-Type unless one was set explicitly.

import { Res, Status } from "@ozanarslan/corpus";

new Res({ id: 1 }, { status: Status.CREATED });
new Res().file("./report.pdf");
new Res().redirect("/login");
Contents
  1. Res
  2. Status

Res

class

class Res<R = unknown>

A response under construction.

Everything is mutable until Res.toNativeResponse is called, so a Middleware can adjust a response a route handler already built. The body is kept as the original value rather than serialized eagerly, which is what lets the content type be inferred from it at the very end.

Headers and cookies are both lazy, so an untouched response allocates neither. The chainable methods — Res.file, Res.redirect, Res.sse and the rest — set the body and its headers together and return this.

Type parameters

Res.constructor()

constructor(body?: Nullable<BodyInit | R>, init?: ResInit)

Creates a response.

Parameters

Res.body

body: Nullable<BodyInit | R>;

The body to send. Assign any value — objects become JSON, typed arrays stay binary, streams pass through — and resolveResBody works out the rest at serialization time.

Res.status

status: number;

The status code. Defaults to Status.OK.

Res.statusText

statusText: string;

The status text. Empty by default, which lets the runtime supply the standard phrase.

Res.headers

get headers(): Headers

The response headers.

Reading them rewrites the Set-Cookie lines from Res.cookies first, so the two views never disagree — and writing Set-Cookie here feeds back into the cookie map. Values may be numbers or booleans, since patchGlobalHeaders has stringified setters.

Returns — The headers, created on first access.

Res.cookies

get cookies(): Cookies

The cookies to send, as a mutable map.

This is the authoritative view: the map is serialized into Set-Cookie whenever Res.headers is read, so deleting a cookie here removes its header. Values are percent-encoded on serialization, which is what keeps a CRLF in a cookie value from splitting the response.

Returns — The Cookies map, created on first access.

Res.toNativeResponse()

toNativeResponse(): Response

Serializes everything into a native Response.

The content type inferred from the body is applied only when none was set explicitly, so a handler's own choice always wins. X-Content-Type-Options: nosniff is set unconditionally — without it a browser will sniff a text/plain body that looks like markup and render it as HTML, turning any reflected value into XSS.

Returns — The response to send over the wire. Called by App.respond.

Res.sse()

sse(source: SseSource, retry?: number): this

Turns the response into a server-sent event stream.

Sets the body to a stream and the headers browsers require for EventSource to work — the event stream type, no caching, and a kept-alive connection.

Parameters

Returns — This response, for chaining.

Res.ndjson()

ndjson(source: NdjsonSource): this

Turns the response into a newline-delimited JSON stream.

Each item is serialized onto its own line, so a client can parse results as they arrive instead of waiting for a whole array. Useful for large result sets and progressive output where the event semantics of Res.sse are not needed.

Parameters

Returns — This response, for chaining.

Res.streamFile()

streamFile(fileOrPath: XFile | string, disposition: ContentDispositionDefinition["disposition"], ): this

Streams a file as the response body, without reading it into memory. Prefer this over Res.file for anything large.

Parameters

Returns — This response, for chaining.

Throws — Exception with Status.NOT_FOUND when the file does not exist.

Res.file()

file(fileOrPath: XFile | string): this

Sends a file as the response body, read into memory so it can carry an exact Content-Length. Use Res.streamFile instead for large files.

Parameters

Returns — This response, for chaining.

Throws — Exception with Status.NOT_FOUND when the file does not exist.

Res.redirect()

redirect(url: string | URL, status: 301 | 302 | 303 | 307 | 308 = 302): this

Redirects the client to another URL.

Parameters

Returns — This response, for chaining.

Res.permanentRedirect()

permanentRedirect(url: string | URL): this

Redirects with Status.MOVED_PERMANENTLY, which browsers and search engines cache indefinitely. Use it only when the resource has really moved for good.

Parameters

Returns — This response, for chaining.

Res.temporaryRedirect()

temporaryRedirect(url: string | URL): this

Redirects with Status.TEMPORARY_REDIRECT, which preserves the original method and body — unlike Status.FOUND, which clients commonly turn into a GET.

Parameters

Returns — This response, for chaining.

Res.seeOther()

seeOther(url: string | URL): this

Redirects with Status.SEE_OTHER, which explicitly switches the client to a GET. This is the correct redirect after a successful POST, since it stops a refresh from resubmitting the form.

Parameters

Returns — This response, for chaining.

Status

const

const Status;
type Status = ValueOf<typeof Status> | (number & {});

Commonly used HTTP status codes.

An HTTP status code. The Status constants are suggested, but any number is assignable.

NameValueDescription
CONTINUE100Continue: Request received, please continue
SWITCHING_PROTOCOLS101Switching Protocols: Protocol change request approved
PROCESSING102Processing (WebDAV)
EARLY_HINTS103Early Hints
OK200OK: Request succeeded
CREATED201Created: Resource created
ACCEPTED202Accepted: Request accepted but not completed
NON_AUTHORITATIVE_INFORMATION203Non-Authoritative Information
NO_CONTENT204No Content: Request succeeded, no body returned
RESET_CONTENT205Reset Content: Clear form or view
PARTIAL_CONTENT206Partial Content: Partial GET successful (e.g. range requests)
MULTI_STATUS207Multi-Status (WebDAV)
ALREADY_REPORTED208Already Reported (WebDAV)
IM_USED226IM Used (HTTP Delta encoding)
MULTIPLE_CHOICES300Multiple Choices
MOVED_PERMANENTLY301Moved Permanently: Resource moved to a new URL
FOUND302Found: Resource temporarily under different URI
SEE_OTHER303See Other: Redirect to another URI using GET
NOT_MODIFIED304Not Modified: Cached version is still valid
USE_PROXY305Use Proxy: Deprecated
TEMPORARY_REDIRECT307Temporary Redirect: Resource temporarily at another URI
PERMANENT_REDIRECT308Permanent Redirect: Resource permanently at another URI
BAD_REQUEST400Bad Request: Malformed request
UNAUTHORIZED401Unauthorized: Missing or invalid auth credentials
PAYMENT_REQUIRED402Payment Required: Reserved for future use
FORBIDDEN403Forbidden: Authenticated but no permission
NOT_FOUND404Not Found: Resource does not exist
METHOD_NOT_ALLOWED405Method Not Allowed: HTTP method not allowed
NOT_ACCEPTABLE406Not Acceptable: Response not acceptable by client
PROXY_AUTHENTICATION_REQUIRED407Proxy Authentication Required
REQUEST_TIMEOUT408Request Timeout: Server timeout waiting for client
CONFLICT409Conflict: Request conflict (e.g. duplicate resource)
GONE410Gone: Resource is no longer available
LENGTH_REQUIRED411Length Required: Missing Content-Length header
PRECONDITION_FAILED412Precondition Failed
PAYLOAD_TOO_LARGE413Payload Too Large
URI_TOO_LONG414URI Too Long
UNSUPPORTED_MEDIA_TYPE415Unsupported Media Type
RANGE_NOT_SATISFIABLE416Range Not Satisfiable
EXPECTATION_FAILED417Expectation Failed
IM_A_TEAPOT418I'm a teapot: Joke response for coffee machines
MISDIRECTED_REQUEST421Misdirected Request: Sent to the wrong server
UNPROCESSABLE_ENTITY422Unprocessable Entity (WebDAV)
LOCKED423Locked (WebDAV)
FAILED_DEPENDENCY424Failed Dependency (WebDAV)
TOO_EARLY425Too Early: Request might be replayed
UPGRADE_REQUIRED426Upgrade Required
PRECONDITION_REQUIRED428Precondition Required
TOO_MANY_REQUESTS429Too Many Requests: Rate limiting
REQUEST_HEADER_FIELDS_TOO_LARGE431Request Header Fields Too Large
UNAVAILABLE_FOR_LEGAL_REASONS451Unavailable For Legal Reasons
INTERNAL_SERVER_ERROR500Internal Server Error: Unhandled server error
NOT_IMPLEMENTED501Not Implemented: Endpoint/method not implemented
BAD_GATEWAY502Bad Gateway: Invalid response from upstream server
SERVICE_UNAVAILABLE503Service Unavailable: Server temporarily overloaded/down
GATEWAY_TIMEOUT504Gateway Timeout: No response from upstream server
HTTP_VERSION_NOT_SUPPORTED505HTTP Version Not Supported
VARIANT_ALSO_NEGOTIATES506Variant Also Negotiates
INSUFFICIENT_STORAGE507Insufficient Storage (WebDAV)
LOOP_DETECTED508Loop Detected (WebDAV)
NOT_EXTENDED510Not Extended
NETWORK_AUTHENTICATION_REQUIRED511Network Authentication Required