SpaceNode
The request object is the first argument to every handler and pipe. It contains all request data and utility methods.
| Property | Type | Description |
|---|---|---|
method | string | HTTP method (GET, POST, etc.) |
path | string | URL pathname |
url | string | Full URL string |
params | object | Route params (/:id → params.id) |
query | object | Query string params (?q=foo → query.q) |
headers | object | Request headers (lowercase keys) |
body | object|null | Parsed body (JSON, URL-encoded form, or multipart fields) |
cookies | object | Parsed incoming cookies (cookies.token, cookies.lang) |
files | array|null | Uploaded files from multipart/form-data (see Body Parsing) |
ip | string | Client IP (uses x-forwarded-for when trustProxy: true) |
db | any | Database reference from config.db |
config | object | App config object |
user | object? | Set by auth guard (merged from pipe result) |
raw | { req, res } | Raw Node.js IncomingMessage and ServerResponse for streaming or advanced use |
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
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
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)
Throw an HttpError directly:
error(403, 'Forbidden')
error(400, 'Bad Request', { field: 'email' })
Emit an event through the global event bus:
await emit('auth:login', { userId: user.id })
await emit('order:created', { orderId, total })
setHeader('X-Custom-Header', 'value')
redirect('https://example.com') // 302
redirect('/new-location', 301) // permanent redirect
html('<h1>Hello</h1>')
cookie('token', 'abc123', {
httpOnly: true,
secure: true,
maxAge: 86400,
sameSite: 'Strict',
path: '/',
})
cookie() multiple times appends to the Set-Cookie header (doesn't overwrite).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.
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.
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
})
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
})
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
})