C.W.K.
Stream
Lesson 01 of 09 · published

Route Handlers

~20 min · route handler, API, REST

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

When you actually need an API route

Server Actions cover internal mutations. Server Components cover internal data reads. Route Handlers cover the rest: webhooks, third-party integrations, and public APIs — anything that exposes a stable URL the outside world can call.

The shape

A route.ts in any folder under app/ exports HTTP method handlers (GET, POST, PUT, DELETE, etc.). Each receives a NextRequest and returns a NextResponse.

Hard rule

A folder with route.ts can't also have page.tsx. They serve different worlds (API vs UI). Pick one per folder.

Code

GET + POST handlers·ts
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET(request: NextRequest) {
  const params = request.nextUrl.searchParams;
  const page = Number(params.get('page') ?? 1);
  const posts = await db.post.findMany({ skip: (page - 1) * 10, take: 10 });
  return NextResponse.json(posts);
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}
Dynamic route handler with params·ts
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const post = await db.post.findUnique({ where: { id } });
  if (!post) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json(post);
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  await db.post.delete({ where: { id } });
  return new NextResponse(null, { status: 204 });
}

External links

Exercise

Build a webhook endpoint at app/api/stripe/route.ts that verifies a Stripe signature header and writes to your database. Test it with the Stripe CLI's stripe listen.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.