API Routes (Route Handlers) in Next.js
Route Handlers (called API Routes in the Pages Router) let you create server-side API endpoints directly in a Next.js application. They are defined in route.ts files inside the app directory.
Basic Example
// app/api/problems/route.ts
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export async function GET() {
const problems = await db.problem.findMany()
return NextResponse.json(problems)
}
export async function POST(request: Request) {
const body = await request.json()
const problem = await db.problem.create({
data: body
})
return NextResponse.json(problem, { status: 201 })
}
Each exported method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) handles the corresponding HTTP request.
Dynamic Route Handlers
// app/api/problems/[id]/route.ts
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const problem = await db.problem.findUnique({
where: { id: params.id }
})
if (!problem) {
return NextResponse.json(
{ error: 'Problem not found' },
{ status: 404 }
)
}
return NextResponse.json(problem)
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
await db.problem.delete({ where: { id: params.id } })
return new Response(null, { status: 204 })
}
Working with Request
Route Handlers use the standard Web API Request:
// app/api/search/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const query = searchParams.get('q')
if (!query) {
return NextResponse.json(
{ error: 'Query parameter required' },
{ status: 400 }
)
}
const results = await db.problem.findMany({
where: { name: { contains: query, mode: 'insensitive' } }
})
return NextResponse.json(results)
}
Reading Headers and Cookies
import { cookies, headers } from 'next/headers'
export async function GET() {
const cookieStore = cookies()
const token = cookieStore.get('session')
const headersList = headers()
const userAgent = headersList.get('user-agent')
return NextResponse.json({ token, userAgent })
}
Caching
GET requests that do not read the Request are cached by default:
// Cached (static Route Handler)
export async function GET() {
const data = await fetch('https://api.example.com/data')
return NextResponse.json(data)
}
// Not cached (reads Request)
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
// ...
}
For explicit cache control:
export const dynamic = 'force-dynamic' // always dynamic
export const revalidate = 300 // revalidate after 5 minutes
Route Handlers vs Server Actions
| Route Handlers | Server Actions | |
|---|---|---|
| Purpose | API endpoints | UI mutations |
| HTTP methods | GET, POST, PUT, DELETE... | POST only |
| Called from | Any client (fetch, curl) | Components only |
| Progressive enhancement | No | Yes (forms) |
When to use which:
Use Server Actions for data mutations from the interface (forms, buttons). Use Route Handlers for public APIs, webhooks, integrations with external services and cases where specific HTTP methods are needed.
Useful Resources
- nextjs.org/docs — Route Handlers
- route.ts API Reference