CodeOath
← All posts
Next.js31 min total · 12 parts

Next.js Fundamentals: The App Router, Server Components, and the Caching Layer Nobody Warns You About

Part 9 of 12 · ~2 min

Route Handlers & API Routes

A Route Handler is a route.ts file inside app/ that exports functions named after HTTP methods, giving you a real JSON API endpoint inside your Next.js app rather than a page:

// app/api/posts/route.ts
export async function GET() {
  const posts = await db.post.findMany();
  return Response.json(posts);
}

export async function POST(request: Request) {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return Response.json(post, { status: 201 });
}
// app/api/posts/[id]/route.ts
export async function GET(request: Request, { params }: { params: { id: string } }) {
  const post = await db.post.findUnique({ where: { id: params.id } });
  if (!post) return Response.json({ error: "Not found" }, { status: 404 });
  return Response.json(post);
}

This is a real Request in, real Response out, full control over status codes, headers, and body shape — the same primitives you'd use to build any HTTP endpoint, just colocated inside your Next.js app instead of a separate backend service.

When a Route Handler is the right tool over a Server Action

The deciding question is almost always who's calling this:

  • Only your own app's UI needs to trigger this mutation, and a plain function call is a natural fit — reach for a Server Action. Less boilerplate, works from a form without JavaScript, no separate client-side fetch to maintain.
  • Something other than your own React components needs to call this — a mobile app hitting the same backend, a third-party webhook (Stripe, GitHub) delivering an event, a public API you're intentionally exposing, a request that isn't shaped like a form submission at all — reach for a Route Handler. It's the one that speaks plain, addressable HTTP to anything that can make a request, not just your own app's rendered forms and components.

A genuinely common real-world shape is both at once: a Route Handler as the actual public API surface (for webhooks, external consumers, mobile clients), and Server Actions layered on top of that same data for your own app's own UI, so you're not hand-rolling fetch calls to your own API from your own frontend just because the API happens to already exist.