100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Route Handlers (API Routes)

Learn how to build server-side API endpoints in the Next.js App Router using route.ts files, the Web Request/Response APIs, dynamic segments, and their caching behavior.

Data FetchingIntermediate9 min readJul 10, 2026
Analogies

What Are Route Handlers

Route Handlers let you define server-side endpoints in the App Router by creating a route.ts (or route.js) file inside app/api/... that exports named async functions matching HTTP verbs like GET, POST, PUT, PATCH, and DELETE, each receiving a standard Web Request object and returning a Response or NextResponse. Unlike the old pages/api pages, route handlers live alongside the route segments they serve, and a folder cannot export both a page and a route handler for the same path segment at once.

🏏

Cricket analogy: Like each fielding position having a dedicated specialist — a slip catcher, a boundary rider — rather than one generic fielder doing every job on the ground.

ts
// app/api/products/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const products = await db.product.findMany();
  return NextResponse.json(products);
}

export async function POST(request: Request) {
  const body = await request.json();
  const product = await db.product.create({ data: body });
  return NextResponse.json(product, { status: 201 });
}

The Request/Response Web APIs and Dynamic Segments

Because route handlers use the standard Web Request and Response objects, you can call request.nextUrl.searchParams to read query strings, request.json() to parse a POST body, and return NextResponse.json({ data }, { status: 201 }) for structured responses; dynamic segments like app/api/orders/[id]/route.ts receive their params via the second function argument as { params: { id: string } }. This makes route handlers a drop-in replacement for a small REST API without needing Express or a separate backend service.

🏏

Cricket analogy: Like reading the exact required run rate off the scoreboard mid-chase rather than guessing it, then updating the board with the new target after every over.

ts
// app/api/orders/[id]/route.ts
import { NextResponse, type NextRequest } from 'next/server';

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const includeItems = request.nextUrl.searchParams.get('includeItems');
  const order = await db.order.findUnique({
    where: { id: params.id },
    include: { items: includeItems === 'true' },
  });
  if (!order) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json(order);
}

Caching Behavior of Route Handlers

By default, a GET route handler using no dynamic functions (no cookies(), headers(), or request.nextUrl beyond static parts) is treated as static and cached at build time, similar to a statically rendered page; adding export const dynamic = 'force-dynamic' or reading from the incoming request forces it to run fresh on every request. POST, PUT, PATCH, and DELETE handlers are never cached, since they're inherently meant to mutate state rather than return the same cached response repeatedly.

🏏

Cricket analogy: Like a printed pre-match scorecard listing the fixed squad list that's reused all day, versus the live ball-by-ball commentary that must be freshly generated for every delivery.

A GET handler that reads cookies() or headers() is automatically opted out of static caching, because those APIs depend on the specific incoming request and can't produce a single reusable response.

Middleware, Headers, and Cookies in Route Handlers

Route handlers can read and set cookies with the cookies() function from next/headers and inspect incoming headers with headers(), both of which automatically opt the route out of static caching since they depend on the specific incoming request. They also run after any matching middleware.ts, so a route handler can rely on middleware having already validated an auth token or rewritten the URL before its own handler function executes.

🏏

Cricket analogy: Like the third umpire only stepping in after the on-field umpire has already made an initial call, reviewing that specific decision rather than officiating from scratch.

Using cookies() or headers() inside a route handler is a common way developers accidentally disable caching for an endpoint that was meant to be static. If you need static caching, avoid these APIs and pass required identifiers as URL search params instead.

  • Route Handlers live in route.ts files inside app/ and export functions named after HTTP verbs like GET and POST.
  • A folder can export a page.tsx or a route.ts for the same segment, but not both.
  • Route handlers use the standard Web Request and Response objects rather than a custom framework API.
  • Dynamic segments deliver their values through a params argument, e.g. { params: { id: string } }.
  • GET handlers with no dynamic APIs are statically cached at build time by default.
  • Reading cookies(), headers(), or using force-dynamic opts a route handler out of static caching.
  • POST, PUT, PATCH, and DELETE handlers are never cached since they perform mutations.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#NextJsStudyNotes#WebDevelopment#RouteHandlersAPIRoutes#Route#Handlers#API#Routes#APIs#StudyNotes#SkillVeris