Route handlers (API) cheatsheet.
Basic GET
// app/api/hello/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ ok: true });
}
All HTTP methods
export async function GET(req: Request) { ... }
export async function POST(req: Request) { ... }
export async function PUT(req: Request) { ... }
export async function DELETE(req: Request) { ... }
export async function PATCH(req: Request) { ... }
export async function OPTIONS(req: Request) { ... }
export async function HEAD(req: Request) { ... }
Exported = handled. Missing = 405.
Dynamic params
// app/api/users/[id]/route.ts
export async function GET(
_req: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const user = await db.user.find(Number(id));
if (!user) return NextResponse.json({ error: "not found" }, { status: 404 });
return NextResponse.json(user);
}
Catch-all params
// app/api/files/[...path]/route.ts
export async function GET(
_req: Request,
{ params }: { params: Promise<{ path: string[] }> },
) {
const { path } = await params; // ["a", "b", "c"]
}
Reading JSON body
export async function POST(req: Request) {
const body = await req.json();
return NextResponse.json({ received: body });
}
FormData
export async function POST(req: Request) {
const fd = await req.formData();
const file = fd.get("file") as File;
return NextResponse.json({ name: file.name, size: file.size });
}
Search params
export async function GET(req: Request) {
const url = new URL(req.url);
const q = url.searchParams.get("q") ?? "";
const page = Number(url.searchParams.get("page") ?? "1");
return NextResponse.json({ q, page });
}
Headers
const ua = req.headers.get("user-agent");
const res = NextResponse.json({ ok: true });
res.headers.set("x-custom", "value");
return res;
Cookies
import { cookies } from "next/headers";
export async function POST() {
const c = await cookies();
c.set("session", "...", { httpOnly: true });
return NextResponse.json({ ok: true });
}
// Read:
const session = (await cookies()).get("session")?.value;
Status codes
return NextResponse.json({ error: "bad" }, { status: 400 });
return NextResponse.json({ ok: true }, { status: 201 });
return new NextResponse(null, { status: 204 });
Redirect
return NextResponse.redirect(new URL("/login", req.url));
return NextResponse.redirect("https://other.com");
Streaming
export async function GET() {
const stream = new ReadableStream({
start(controller) {
let i = 0;
const id = setInterval(() => {
controller.enqueue(`event: ${i++}\n\n`);
if (i > 5) { clearInterval(id); controller.close(); }
}, 1000);
},
});
return new NextResponse(stream, {
headers: { "Content-Type": "text/event-stream" },
});
}
Server-sent events / streaming responses.
Returning files
export async function GET() {
const buf = await fs.readFile("./file.pdf");
return new NextResponse(buf, {
headers: { "Content-Type": "application/pdf" },
});
}
Zod validation
import { z } from "zod";
const Body = z.object({ name: z.string(), email: z.string().email() });
export async function POST(req: Request) {
const parsed = Body.safeParse(await req.json());
if (!parsed.success) {
return NextResponse.json({ errors: parsed.error.flatten() }, { status: 400 });
}
const user = await db.user.create({ data: parsed.data });
return NextResponse.json(user, { status: 201 });
}
CORS
const cors = {
"Access-Control-Allow-Origin": "https://app.example.com",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
export async function OPTIONS() {
return new NextResponse(null, { status: 204, headers: cors });
}
export async function GET() {
return NextResponse.json({}, { headers: cors });
}
Auth in route handler
import { auth } from "@/auth";
export async function GET() {
const session = await auth();
if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
return NextResponse.json({ user: session.user });
}
Caching responses
export const dynamic = "force-static";
export const revalidate = 60;
export async function GET() {
const data = await fetchExternal();
return NextResponse.json(data);
}
Caches the route output. Mostly useful for proxy / data-derived endpoints.
Edge runtime
export const runtime = "edge";
export async function GET(req: Request) {
return NextResponse.json({ hello: "edge" });
}
Faster cold start, smaller bundle, fewer Node APIs.
Common mistakes
- Forgetting
await paramsin Next 15+. - Returning
Responsewith non-JSON header but JSON body. - Missing CORS preflight handler.
cache: "force-cache"on per-user data.- Not validating body — accepts garbage, crashes.
Read this next
If you want my route handlers + Zod + auth template, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .