HTTP@rhythmjs/http
API reference
Every export of the thirteen @rhythmjs/http subpath modules, from cookies and sessions to compression, uploads, and request-scoped storage.
On this page
@rhythmjs/http/cookies#
cookies() returns middleware that parses the request Cookie header with Bun's native Bun.CookieMap and passes { cookies: Cookies } to downstream middleware. CookiesContext is the exported context type for that extension.
function cookies(): Middleware<RhythmHttpContext>Cookies.get(name)#- Return the decoded request cookie value, or undefined when the cookie is absent.
Cookies.has(name)#- Whether a request cookie with that name exists.
Cookies.getAll()#- Return a copy of all request cookies as a plain record.
Cookies.set(name, value, options?)#- Append a Set-Cookie header serialized by Bun's native
Bun.Cookie. Bun's defaults apply (Path=/andSameSite=Lax) unless overridden. Each call appends a separate header. Cookies.delete(name, options?)#- Expire a cookie by setting an empty value with
Max-Age=0; anyexpiresormaxAgein the options is overridden.
CookieOptions is Bun's CookieInit minus name/value, all fields optional: domain, path, expires (number, Date, or string), maxAge (seconds), httpOnly, secure, sameSite ("strict", "lax", or "none"), and partitioned.
Two standalone helpers are exported as well.
parseCookies(header)#- Parse a Cookie header string (or null) into a record of decoded name and value pairs. Quoted values are unquoted, and values that fail URI decoding are kept as-is.
serializeCookie(name, value, options?)#- Build a single Set-Cookie header value from a name, a value, and
CookieOptionsattributes.
@rhythmjs/http/session#
session(options?) returns middleware that loads the session named by the request's session cookie and passes { session: Session } downstream. SessionContext is the exported context type. An unknown or expired id gets a fresh session with a new random UUID, so a forged cookie is never trusted as-is. The session is persisted, and the cookie written, only after a write; the cookie is emitted just once, when the session is first stored.
function session(options?: SessionOptions): Middleware<RhythmHttpContext>SessionOptions fields, all optional:
store: anySessionStoreimplementation. Default:memorySessionStore().cookieName: session cookie name. Default:"sid".maxAge: lifetime in seconds for the cookie and the stored session. Default: 86400.path: cookie path. Default:"/".secure: add the Secure attribute. Default: off.sameSite:"strict","lax", or"none". Default:"lax". The cookie is alwaysHttpOnly.
Session#- The
ctx.sessioninterface: readonlyid,get<T>(key),set(key, value),delete(key), anddestroy().destroy()deletes the stored session after the handler returns and expires the cookie withMax-Age=0. SessionStore#- The storage interface:
get(id)resolving toSessionDataor undefined,set(id, data, maxAge), anddelete(id). Each method may be sync or return a promise. memorySessionStore()#- Returns the bundled in-process
SessionStore. Entries expire aftermaxAgeseconds and are dropped on read once expired. Suitable for a single process. SessionData#- The stored shape:
Record<string, unknown>. SessionContext#- The context extension type:
{ session: Session }.
@rhythmjs/http/etag#
etag(options?) returns middleware that runs after downstream handlers and sets an ETag header computed from the SHA-1 hash of the response body, synchronously, via Bun's native Bun.CryptoHasher. It only acts on 2xx responses with string bodies, and it leaves a pre-set ETag header untouched. When the request's If-None-Match header lists the computed tag, the response becomes 304 Not Modified with a null body and no Content-Length.
function etag(options?: EtagOptions): Middleware<RhythmHttpContext>EtagOptions has one optional field: weak (boolean). When true, tags are emitted with the W/ weak-validator prefix.
@rhythmjs/http/timeout#
timeout(ms) returns middleware that races the downstream chain against a deadline of ms milliseconds. When the deadline wins, the response becomes status 504 with a content-type: application/json header and the body { "success": false, "status": 504, "message": "Gateway Timeout" }.
function timeout(ms: number): Middleware<RhythmHttpContext>- Downstream errors thrown before the deadline still propagate as errors; only the deadline itself produces a 504.
- Late downstream work is not aborted. It keeps running, its result is discarded, and a late rejection is swallowed.
@rhythmjs/http/body-limit#
bodyLimit(maxBytes) returns middleware that rejects request bodies larger than maxBytes with status 413 and the JSON body { "success": false, "status": 413, "message": "Payload Too Large" }. Downstream middleware is not called for a rejected request.
function bodyLimit(maxBytes: number): Middleware<RhythmHttpContext>- When a valid
Content-Lengthheader is present, its value is compared against the limit without reading the body. - Otherwise, when the request has a body, the stream is counted and cancelled as soon as it exceeds the limit (never holding more than the limit plus one chunk); accepted bodies are re-exposed buffered on
ctx.requestfor downstream handlers.
@rhythmjs/http/compress#
compress(options?) returns middleware that content-negotiates response compression on Bun's native compressors.
function compress(options?: CompressOptions): Middleware<RhythmHttpContext>CompressOptions.threshold#- Minimum buffered body size in bytes (default 1024). Streams are always compressed, their size being unknown.
CompressOptions.encodings#- Preference order offered to the client; default
["zstd", "gzip", "deflate"](CompressEncoding). Streaming bodies never pickzstd;deflatealways streams throughCompressionStream, sinceBun.deflateSyncemits raw deflate rather than the zlib-wrapped HTTP coding. CompressOptions.filter#- Replace the default compressible-type check (text, JSON, JavaScript, XML, SVG, wasm) with your own
(contentType) => boolean.
It never touches 204/304, empty or already-encoded responses, or text/event-stream, and appends Vary: Accept-Encoding where negotiation applies.
@rhythmjs/http/cache#
function cacheControl(options: CacheControlOptions): Middleware<RhythmHttpContext>
function formatCacheControl(options: CacheControlOptions): string
function cache(options?: CacheOptions): Middleware<RhythmHttpContext>
function memoryCacheStore(options?: MemoryCacheStoreOptions): CacheStoreCacheControlOptions#maxAge,sMaxAge,staleWhileRevalidate,staleIfError(numbers, seconds);public,private,noCache,noStore,mustRevalidate,immutable(booleans).cacheControl()only writes the header when none is set downstream.CacheOptions#ttl(seconds, default 60),store(CacheStore:get/set/delete),keyOf(request)(defaultmethod + path + search),vary(header names folded into the key), andfilter(ctx)(veto storing per response).CacheEntry#- What a store holds:
status,headers,body(bytes),storedAt. Hits replay withAgeandX-Cache: HIT; only200responses withoutno-store/privatecache-control are stored, never ones carryingSet-Cookie, stream bodies, or answers toAuthorizationrequests without an explicitpublic/s-maxage/must-revalidate. MemoryCacheStoreOptions#maxEntries(default 1024) caps the LRU entry count;maxEntryBytes(default 1 MiB) refuses oversized bodies.
@rhythmjs/http/mount#
mount(path, handler) returns middleware that runs handler only for requests whose path matches, and passes every other request on untouched. It exists for fetch-style handlers from other libraries: mount("/api/auth/**", (ctx) => auth.handler(ctx.request)). The guide is Mounting handlers.
path follows the same rou3 conventions as the router: static segments, :name parameters, * for one segment and ** as the catch-all. "/api/auth/**" matches /api/auth and everything beneath it, but not /api/authx. The HTTP method is not part of the match, and a path that does not start with / throws a TypeError.
function mount<TContext extends RhythmHttpContext>(path: string, handler: MountHandler<TContext>): Middleware<TContext>
type MountHandler<TContext> = (ctx: TContext, next: NextFn<TContext>) => Response | void | Promise<Response | void>handler returns a Response#- Its status, status text, headers and body stream become the response and the chain stops. Every
Set-Cookieheader is kept; headers set earlier in the chain stay unless the Response sets them again. handler returns nothing#- The handler decides for itself, like any middleware, and may call
next().
@rhythmjs/http/multipart#
function multipart(options?: MultipartOptions): DeriveMiddleware<RhythmHttpContext, MultipartContext>
class MultipartForm
type RequestFormData = Awaited<ReturnType<Request["formData"]>>
type FormFile = Exclude<ReturnType<RequestFormData["get"]>, string | null>MultipartOptions#maxBytes(whole body, enforced while streaming),maxFileSize(per file),maxFiles,maxFields. Violations answer413; non-multipart content types415; a missing or malformed body400.MultipartForm#- On
ctx.form:data(the rawRequestFormData),get(name)andgetAll(name)(string fields only), andfile(name)andfiles(name?), with files typed asFormFile.
@rhythmjs/http/sse and /stream#
function sse(options?: SseOptions): Middleware<RhythmHttpContext>
function stream(options?: StreamOptions): Middleware<RhythmHttpContext>Both set long-lived-response defaults after the handler runs, only where a header is not already set: sse() uses content-type: text/event-stream; stream() uses text/plain and adds x-content-type-options: nosniff. Both set cache-control: no-cache, no-transform, connection: keep-alive, and x-accel-buffering: no. The single headers option force-overrides any of them.
@rhythmjs/http/i18n#
function i18n<T extends I18nInstanceLike>(options: I18nOptions<T>): DeriveMiddleware<RhythmHttpContext, I18nContext<T>>
function detectLanguage(request: Request, options?: LanguageDetectionOptions): string | undefined
function parseAcceptLanguage(header: string | null): AcceptedLanguage[]I18nOptions#i18next(anyI18nInstanceLike: a structural contract where onlytis required, so i18next major versions cannot break compilation),detection,cacheCookie+cacheCookieMaxAge(persist the detected language, default one year), andcontentLanguage(set the response header; default on).LanguageDetectionOptions#order(default["querystring", "cookie", "header"];"path"also available),lookupQuerystring(defaultlng),lookupCookie(defaulti18next),lookupPath(segment index),supportedLanguages, andfallbackLanguage; the last two default to the instance's own configuration. Matching tries exact tags, then base languages, case-insensitively.I18nContext#ctx.i18n(the per-request instance: a clone when the instance supports it, elsegetFixedT),ctx.t, andctx.language.
@rhythmjs/http/request-scope#
function requestScope<T extends RhythmHttpContext>(): Middleware<T>
class RequestScope
class RequestScopeError extends Error
interface RequestScopeStore {} // augment to type keysRequestScope.context() / contextOrNull()#- The current request's context from anywhere on its async path; the first throws
RequestScopeErroroutside a scope, the second returns null. RequestScope.get / set / has / delete#- A per-request key-value store keyed by string or symbol; typed keys come from augmenting
RequestScopeStore. RequestScope.isActive() / RequestScope.run(context, fn)#- Whether a scope is open, and a manual scope for tests or background work.