Have you heard about Hack Frontend Community?Join us on Telegram!

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

By continuing to use the platform, you accept the terms of the Privacy Policy and the use of cookies.