Documentation
Docs
Introduction
Getting Started
Tutorial: build an app
Core Concepts
Routing
Server-Side Rendering
Mimir, state management
Pages & Layouts
API Routes
Styling & Theming
Building & Deploying
API Reference
Guides
Recipes
FAQ (use cases)
Request and response
API handlers receive (req, res). This page is the reference for both.
The request , req
| Field | Type | What |
|---|---|---|
req.params | object | Path params from :param segments (/api/notes/:id → { id }). |
req.query | string | The raw query string ("?tab=a&x=1"), parse it yourself (see below). |
req.path | string | The request pathname. |
req.body | ReadableStream | null | The request body as a stream — read with getReader() to pipe a large upload without buffering. |
await req.json() | any | Parses the body as JSON. Async, content-type-independent, single-use; throws on invalid JSON. |
await req.text() | string | The body decoded as UTF-8 text. |
await req.bytes() | Uint8Array | The body as raw bytes. |
await req.arrayBuffer() | ArrayBuffer | The body as an ArrayBuffer. |
req.user | object | null | A user object, if middleware attached one. |
req.headers | object | Request headers. |
Params
Query string
req.query is the raw string, not a parsed object. Parse it with URLSearchParams:
This is a deliberate, documented behaviour:
req.queryis the raw"?k=v&k2=v2"string, not a pre-parsed object. UseURLSearchParams(or your own parser) so you control coercion and defaults.
Body
The body accessors are async — you MUST await them, and the handler must be async. This is the
WHATWG Request interface (the model Deno and Bun use). req.json() returns a Promise, not the parsed
value — const data = req.json() gives you a Promise (a bug); const data = await req.json() gives you the
data. Each accessor is content-type-independent and single-use (call exactly one):
await req.json()— parse JSON (throws on invalid)await req.text()— UTF-8 textawait req.bytes()—Uint8Arrayawait req.arrayBuffer()—ArrayBufferreq.body— the rawReadableStream(ornull); usegetReader()to stream a large upload
await req.json()parses the body and throws on invalid JSON (catch it if you want to return a 400);await req.text()gives UTF-8 text;await req.bytes()/await req.arrayBuffer()give the raw bytes.req.bodyitself is aReadableStream(ornullwhen there is no body) — read it withgetReader()to stream a large upload chunk-by-chunk without buffering it in memory. A body can be consumed only once.
Validate it (do not trust client input), see Validation & options.
User
If a middleware authenticates the request and sets req.user, your handler reads it:
The response , res
| Method | Effect |
|---|---|
res.json(value) | Send value as application/json. |
res.send(string) | Send a string body. |
res.status(code) | Set the HTTP status (chainable / before sending). |
res.header(name, value) | Set a response header. |
res.html(string) | Send an HTML body (used by page rendering). |
Return value as a shortcut
You rarely need res for JSON, return the value and rune sends it:
Use res directly when you need a non-default status, custom headers, or a non-JSON body.
Status code conventions
| Situation | Status |
|---|---|
| Success | 200 (the default) |
| Created a resource | res.status(201) |
| Bad input | res.status(400) |
| Unauthorized / forbidden | res.status(401) / 403 |
| Not found | res.status(404) |
| Wrong method on a known path | 405 (automatic, see Validation & options) |
| Unhandled throw | 500 (automatic) |
A complete CRUD handler set
Next: cross-cutting request handling, Middleware.