IngeniumContext
IngeniumContext is the single argument your handlers receive. It's a typed, pooled object with lazy getters for things like query and body, chainable setters for response headers, and explicit writers for when you want full control.
API surface
class IngeniumContext<Params = Record<string, string>> {
// Request
method: HttpMethod // 'GET' | 'POST' | ...
url: string // path + ?query
path: string // no query
rawQuery: string
query: URLSearchParams // lazy
params: Params // route params
headers: IncomingHttpHeaders // lowercased per Node convention
body: IngeniumBody // lazy parsers
state: Record<string, unknown> // per-request scratch
// Network info (trust-proxy aware)
ip: string // client IP (XFF-aware if trustProxy enabled)
ips: readonly string[] // full forwarded chain
protocol: 'http' | 'https'
secure: boolean
hostname: string
remoteAddress: string // immediate socket peer
baseProtocol: 'http' | 'https' // underlying transport
// Response setters (chainable)
status(code: number): this
set(name: string, value: string | string[]): this
setHeader(name: string, value: string | string[]): this
getHeader(name: string): string | string[] | undefined
// Response writers
json(body: unknown, status?: number): void
text(body: string, status?: number): void
html(body: string, status?: number): void
send(body: Buffer | string, status?: number): void
redirect(location: string, status?: number): void // default 302
stream(readable: Readable, contentType?: string): void
}
The class is pool-bound: one instance per pool slot, reused across requests. reset() zeros every field by reassignment to keep the V8 hidden class stable.
Response reflection
Handlers can return a value instead of calling a writer:
| Return value | Wire output |
|---|---|
undefined / null | 204 No Content |
string starting < | 200 text/html |
other string | 200 text/plain |
Buffer / Uint8Array | 200 application/octet-stream |
Readable | 200 streamed |
| any object/array | 200 application/json |
If a ctx.json/text/html/send/redirect/stream helper has been called, the return value is ignored.
Per-request state
ctx.state is the scratch space for per-request data - request IDs, the resolved user, timing marks, anything you want to thread through middleware and handlers. It's a plain Record<string, unknown> that's cleared between requests when the context is recycled.
For typed access patterns (ctx.user, ctx.session), use a plugin decorator and a module augmentation rather than poking values onto ctx.state.
Header injection guard
ctx.set(name, value) rejects any value containing \r\n immediately and throws IngeniumHeaderInjectionError. This catches CRLF injection at the call site rather than deep inside Node's wire path.
Where to next?
- Body parsing -
ctx.body.json(), multipart, schema validation. - Trust-proxy - making
ctx.ipand friends honorX-Forwarded-*. - Plugins - decorating
ctxwith typed fields. - Errors - what gets thrown and how to catch it.