app/Next.js API routes in the App Router are called Route Handlers and live inside route.ts files. They let you create custom HTTP endpoints for webhooks, mobile clients, OAuth callbacks, and third-party API integrations using standard Web Request and Response objects.
Export async functions named after HTTP methods (GET, POST, PUT, PATCH, DELETE) from any folder under app/api/.
flowchart LR
A["HTTP Request"] --> B["app/api/items/route.ts"]
B --> C{"HTTP Method"}
C -- "GET" --> D["Run GET() Handler"]
C -- "POST" --> E["Run POST() Handler"]
D & E --> F["Return NextResponse.json()"]
GET and POST Route HandlersReturn structured JSON and explicit HTTP status codes using NextResponse.json().
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Item {
id: number;
name: string;
}
const items: Item[] = [
{ id: 1, name: 'Mechanical Keyboard' },
{ id: 2, name: 'USB-C Hub' },
];
export async function GET() {
return NextResponse.json({ items }, { status: 200 });
}
export async function POST(request: NextRequest) {
const body = await request.json();
if (!body?.name || typeof body.name !== 'string') {
return NextResponse.json({ error: 'Item name is required' }, { status: 400 });
}
const created: Item = { id: Date.now(), name: body.name.trim() };
items.push(created);
return NextResponse.json({ item: created }, { status: 201 });
}
Read search parameters via request.nextUrl.searchParams and dynamic URL segments via the second context argument.
// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const currency = request.nextUrl.searchParams.get('currency') ?? 'USD';
return NextResponse.json({
id,
currency,
});
}
Use Server Actions for form submissions and UI mutations inside your Next.js frontend. Use route.ts Route Handlers when external services, Stripe webhooks, or mobile apps need an HTTP endpoint.
page.tsx and route.ts in the same folder: A single URL path cannot serve both a React page and a Route Handler; keep API files under app/api/.fetch('/api/items') from a Server Component: Server Components already run on the server, so import your data helper directly instead of making an extra HTTP loop.request.json(): Unvalidated payloads can crash handlers when clients send malformed JSON or missing fields.GET Route Handlers cached by default in Next.js 15+?No, starting in Next.js 15, GET handlers are uncached by default unless you export export const dynamic = 'force-static';.
route.ts?Read the raw body with await request.text() before verifying HMAC headers from providers like Stripe or GitHub.
Yes, create a response with NextResponse.json(...) and call response.cookies.set('session', token, { httpOnly: true, path: '/' }).
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With