Request & Response in Handlers
GET, POST, PUT, PATCH, DELETE — receives the standard Web platform Request object and must return a standard Response object (or Next.js's enhanced NextResponse). There's no framework-specific request or response type to learn first; if you know the fetch API, you already know most of this.Request/Response types, the same mental model works whether the handler runs on Node.js or the Edge Runtime.Reading query parameters
Request object's URL is a plain string, so the easiest way to pull out query parameters is to construct a URL instance and read its searchParams.app/api/search/route.ts
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const query = searchParams.get('q') ?? ''
const page = Number(searchParams.get('page') ?? '1')
const results = await runSearch(query, page)
return NextResponse.json({ query, page, results })
}Reading the request body
POST, PUT, and PATCH requests, the request body is read asynchronously off the Request object. Call .json() when the client sent JSON, or .formData() when it sent an HTML form submission (including file uploads).app/api/comments/route.ts — JSON body
export async function POST(request: Request) {
const body = await request.json()
if (!body.text || typeof body.text !== 'string') {
return new Response('Missing "text" field', { status: 400 })
}
const comment = await createComment(body.text)
return Response.json(comment, { status: 201 })
}app/api/upload/route.ts — form data
export async function POST(request: Request) {
const formData = await request.formData()
const file = formData.get('file') as File | null
const title = formData.get('title')
if (!file) {
return new Response('No file provided', { status: 400 })
}
const buffer = await file.arrayBuffer()
await saveUpload(title, buffer)
return Response.json({ success: true })
}Method | Typical body format | Read with |
|---|---|---|
fetch() with JSON | application/json |
|
HTML | application/x-www-form-urlencoded or multipart/form-data |
|
Plain text / webhooks | text/plain, application/octet-stream |
|
Setting headers and status codes
NextResponse extends the Web Response with convenience methods, but plain Response works fine too. Both let you set a status code and headers directly.app/api/status/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json(
{ status: 'ok' },
{
status: 200,
headers: {
'Cache-Control': 'no-store',
'X-App-Version': '1.4.0',
},
}
)
}NextResponse.json(data, init) is a shortcut for new NextResponse(JSON.stringify(data), { ...init, headers: { 'content-type': 'application/json' } }). Use it whenever you're returning JSON — it's shorter and sets the content type for you.A worked, combined example
Here's a small "update a task" handler that reads a route param, validates a JSON body, and returns different statuses for different outcomes — a realistic shape for most API endpoints.
app/api/tasks/[id]/route.ts
import { NextResponse } from 'next/server'
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const body = await request.json().catch(() => null)
if (!body || typeof body.completed !== 'boolean') {
return NextResponse.json(
{ error: 'Expected a boolean "completed" field' },
{ status: 400 }
)
}
const task = await findTask(params.id)
if (!task) {
return NextResponse.json({ error: 'Task not found' }, { status: 404 })
}
const updated = await updateTask(params.id, { completed: body.completed })
return NextResponse.json(updated, { status: 200 })
}Route Handlers receive the Web
Requestobject and return a WebResponse(orNextResponse) — no custom framework types.Read query parameters via
new URL(request.url).searchParams.Read a JSON body with
request.json(), or a form submission (including files) withrequest.formData().NextResponse.json(data, init)is a shortcut for building a JSON response with the right content type, status, and headers.Return early with the right status code (400, 404, 401, etc.) — treat handlers like any other well-behaved HTTP endpoint.