Request Context

The request object is the first argument to every handler and pipe. It contains all request data and utility methods.

Request Properties

PropertyTypeDescription
methodstringHTTP method (GET, POST, etc.)
pathstringURL pathname
urlstringFull URL string
paramsobjectRoute params (/:idparams.id)
queryobjectQuery string params (?q=fooquery.q)
headersobjectRequest headers (lowercase keys)
bodyobject|nullParsed body (JSON, URL-encoded form, or multipart fields)
cookiesobjectParsed incoming cookies (cookies.token, cookies.lang)
filesarray|nullUploaded files from multipart/form-data (see Body Parsing)
ipstringClient IP (uses x-forwarded-for when trustProxy: true)
dbanyDatabase reference from config.db
configobjectApp config object
userobject?Set by auth guard (merged from pipe result)
raw{ req, res }Raw Node.js IncomingMessage and ServerResponse for streaming or advanced use

Utility Methods

send(data) / send(status, data)

Send a JSON response:

send({ users })                 // 200 + JSON
send(201, { id: newUser.id })    // 201 + JSON
send(204)                        // 204 No Content
send('plain text response')      // 200 + text/plain

check(condition, status, message)

Assertion — throws HttpError if condition is falsy:

const user = await db.findById(id)
check(user, 404, 'User not found')
// If user is null/undefined/false → throws HttpError(404)
// If user exists → returns user and continues

guard(condition, status, message)

Inverse assertion — throws if condition is truthy:

const existing = await db.findByEmail(email)
guard(existing, 409, 'Email already registered')
// If existing is truthy → throws HttpError(409)

error(status, message, details?)

Throw an HttpError directly:

error(403, 'Forbidden')
error(400, 'Bad Request', { field: 'email' })

emit(event, data)

Emit an event through the global event bus:

await emit('auth:login', { userId: user.id })
await emit('order:created', { orderId, total })

setHeader(key, value)

setHeader('X-Custom-Header', 'value')

redirect(url, status?)

redirect('https://example.com')       // 302
redirect('/new-location', 301)        // permanent redirect

html(content, status?)

html('<h1>Hello</h1>')

cookie(name, value, options?)

cookie('token', 'abc123', {
  httpOnly: true,
  secure: true,
  maxAge: 86400,
  sameSite: 'Strict',
  path: '/',
})
Multiple cookies — Calling cookie() multiple times appends to the Set-Cookie header (doesn't overwrite).

Raw Access (Streaming)

request.raw gives you direct access to the underlying Node.js IncomingMessage (req) and ServerResponse (res). Use it when you need streaming, Server-Sent Events, or proxying — scenarios where send() (which buffers the full response) isn't suitable.

Important: When using request.raw to send a response directly, you must set request._sent = true to prevent the framework from sending an automatic 204 after your handler.

File Streaming

import { createReadStream } from 'node:fs'

app.setRoute('GET', '/download/:file', (request) => {
  const { res } = request.raw
  res.writeHead(200, { 'Content-Type': 'application/octet-stream' })
  createReadStream(`./files/${request.params.file}`).pipe(res)
  request._sent = true
})

Server-Sent Events (SSE)

app.setRoute('GET', '/events', (request) => {
  const { res } = request.raw
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  })

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`)
  }, 1000)

  request.raw.req.on('close', () => clearInterval(interval))
  request._sent = true
})

Request Proxying

import http from 'node:http'

app.setRoute('POST', '/proxy', async (request) => {
  const { req, res } = request.raw
  const upstream = http.request('http://backend/api', { method: 'POST' })
  req.pipe(upstream)
  upstream.on('response', (upstreamRes) => {
    res.writeHead(upstreamRes.statusCode, upstreamRes.headers)
    upstreamRes.pipe(res)
  })
  request._sent = true
})