What is a Route Handler?
A route.ts (or .js) file inside app/ defines a backend API endpoint instead of a page. It exports functions named after HTTP methods.
// app/api/students/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
export async function GET() {
const students = await db.students.findMany();
return NextResponse.json(students);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const student = await db.students.create({ data: body });
return NextResponse.json(student, { status: 201 });
}
This creates a real HTTP endpoint at /api/students that any client — your own frontend, a mobile app, or a third party — can call.
Dynamic route params in a handler
// app/api/students/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const student = await db.students.findUnique({ where: { id: Number(id) } });
if (!student) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(student);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.students.delete({ where: { id: Number(id) } });
return new NextResponse(null, { status: 204 });
}
Reading query parameters and headers
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const department = searchParams.get('department'); // /api/students?department=CSE
const authHeader = request.headers.get('authorization');
const students = await db.students.findMany({
where: department ? { department } : undefined,
});
return NextResponse.json(students);
}
Route Handlers vs Server Actions — which to use?
| Route Handler | Server Action | |
|---|---|---|
| Best for | Public APIs, webhooks, third-party consumers, non-Next.js clients | Mutations triggered from your own UI (forms, buttons) |
| Called from | Any HTTP client (fetch, curl, mobile app) | Directly from React components as a function |
| URL exposed | Yes, a real REST endpoint | No separate URL — invoked via a framework-generated action id |
Use a Route Handler when you need an endpoint external systems can call (a webhook receiver, a public API, a mobile app backend). Use a Server Action when the only caller is your own Next.js UI.