Skip to main content
Practice Problems

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

tsx
// 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

tsx
// 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:

tsx
// 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

tsx
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:

tsx
// 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:

tsx
export const dynamic = 'force-dynamic' // always dynamic export const revalidate = 300 // revalidate after 5 minutes

Route Handlers vs Server Actions

Route HandlersServer Actions
PurposeAPI endpointsUI mutations
HTTP methodsGET, POST, PUT, DELETE...POST only
Called fromAny client (fetch, curl)Components only
Progressive enhancementNoYes (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

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems