NextjsRequest & Response in Handlers

Request & Response in Handlers

Every Route Handler function — 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.
Note
Because Route Handlers use the Web Request/Response types, the same mental model works whether the handler runs on Node.js or the Edge Runtime.
Reading query parameters
The incoming 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

TSX
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
For 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

TSX
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

TSX
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

request.json()

HTML <form> submit

application/x-www-form-urlencoded or multipart/form-data

request.formData()

Plain text / webhooks

text/plain, application/octet-stream

request.text()

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

TSX
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',
      },
    }
  )
}
Tip
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

TSX
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 Request object and return a Web Response (or NextResponse) — 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) with request.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.