Reference
Req & Res
Every handler receives two arguments. req reads the incoming request with params, query, body, headers and Cloudflare bindings. res builds the response with a rendered page, JSON, a redirect or a status code. A handler reads from req, then returns something built with res.
export async function POST(req, res) {
const { email } = await req.body();
if (!email) return res.status(400).json({ error: "Email is required" });
return res.json({ ok: true });
}A handler must return a Response (or a Promise<Response>). Every res method produces one to return it. Forgetting to return throws a clear framework error.
Route params
Dynamic segments arrive on req.params, already URL-decoded. For src/routes/polls/[id].jsx matching /polls/123:
req.params.id; // "123"Query strings
req.query() is the friendly reader. It returns a plain object you can destructure. Single values are strings, repeated values are arrays, missing ones are undefined:
export function GET(req, res) {
const { page, sort } = req.query(); // /posts?page=2&sort=new
const { tag } = req.query(); // ?tag=react&tag=cloudflare → ["react", "cloudflare"]
return res.json({ page, sort, tag });
}For native behavior like ordering, .has(), precise repeated-key control. Reach for req.searchParams, which is the URL's URLSearchParams:
const page = req.searchParams.get("page");
const tags = req.searchParams.getAll("tag");Reading the body
req.body() is the friendly body reader. You have to await it and it always resolves to a plain object you can safely destructure, so you never need a ?? {} guard.
export async function POST(req, res) {
const { title } = await req.body();
// ...
}It parses application/json and application/x-www-form-urlencoded. Empty bodies, GET/HEAD/OPTIONS requests, multipart, unsupported content types and even malformed JSON all resolve to {}. Repeated form fields become arrays, so normalize a field that can be single or repeated:
const { option } = await req.body();
const options = Array.isArray(option) ? option : option ? [option] : [];Headers, method and URL
req.headers; // native Headers — req.headers.get("authorization")
req.method; // "GET", "POST", …
req.url; // full request URL as a stringRender a page
res.render takes a component reference and props, never JSX. See Rendering.
export async function GET(req, res) {
const poll = await req.d1.get("SELECT * FROM polls WHERE id = ?", [req.params.id]);
if (!poll) return res.notFound();
return res.render(PollPage, { poll }); // not res.render(<PollPage />)
}JSON
return res.json({ ok: true });
return res.json({ created: true }, 201); // status as the second argumentRedirect
return res.redirect("/thanks"); // 302 by default
return res.redirect("/login", 303); // or pass a statusStatus codes
res.status(code) is chainable and sets the status for the next response method.
return res.status(201).json({ created: true });A status passed directly to json, text or html overrides a pending status().
Not found
res.notFound() renders your registered _404 page with a 404 status. Use it for resource 404s, a route matched but the record is missing. See Error Pages.
if (!post) return res.notFound();Going deeper
Strict JSON
req.json() is the strict counterpart to req.body(): use it when an endpoint must receive JSON and invalid input should fail loudly. It returns the parsed value, {} for an empty body, throws 415 for a non-JSON Content-Type and 400 for malformed JSON.
const { email } = await req.json();Use req.body() for forgiving app handlers, req.json() for strict API endpoints.
Forms and uploads
req.form() is the precise form reader for when repeated values, ordering or file uploads matter. It returns URLSearchParams for urlencoded bodies and FormData for multipart bodies, and throws 415 for anything else.
const form = await req.form();
const title = form.get("title");
const options = form.getAll("option");Bodies over 10 MiB are rejected with 413 Payload Too Large before they are buffered. For larger uploads, stream from req.request.body or upload directly to R2.
Raw bodies
When the exact bytes matter like Stripe's webhook signature verification, binary payloads, read the body raw:
const raw = await req.text(); // raw body as text
verifySignature(raw, req.headers.get("stripe-signature"));
const bytes = await req.arrayBuffer(); // raw body as bytesAll body readers are lazy and cached: each reads from its own clone, so mixing req.body(), req.text() and the rest in one handler is safe and never re-reads the request.
Cloudflare bindings
req.env is the raw Cloudflare environment. Every binding, var and secret from your Cloudflare configuration lives there.
const key = req.env.STRIPE_KEY;
await req.env.MY_QUEUE.send({ type: "signup" });Goribu wraps the common ones in helpers. Use those for app code and drop to req.env for the native binding:
req.d1; // D1 helper — see /docs/d1
req.postgres; // Hyperdrive Postgres helper — see /docs/postgres
req.cache; // Cache API helper — see /docs/cachereq.request is the underlying native Request, for when a third-party library needs the original.
Other response helpers
res.text(body, status?); // text/plain
res.html(body, status?); // text/html, for ad-hoc HTML strings
res.vary(token); // declare a header the response varies on; chainableres.vary accumulates across middleware and chains onto a render:
return res.vary("Cookie").render(Page, props);