Dynamic API Routes
Use [param] folders in API routes and read the value from the async context params.
Syntax
app/api/users/[id]/route.tsRoute Handlers support the same dynamic segments as pages. A folder like [id] captures part of the URL, delivered through the handler's second argument. As with pages in Next 15, params is a Promise you must await.
Example
// app/api/users/[id]/route.ts -> GET /api/users/42
import { NextResponse } from 'next/server';
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await getUser(id);
return NextResponse.json(user);
}When to use it
- A /api/users/[id] handler returns, updates, or deletes a specific user based on the id in the URL.
- A file download endpoint at /api/files/[filename] reads the filename param to stream the correct asset.
- A versioned API uses /api/[version]/resource to serve different response shapes for v1 and v2 clients.
More examples
Dynamic param in route handler
The params argument in a dynamic route handler gives typed access to the URL segment captured by [id].
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await db.user.findUnique({ where: { id } });
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json(user);
}PUT update by ID
Exporting PUT alongside GET on the same dynamic route enables full CRUD with matching HTTP semantics.
// app/api/posts/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function PUT(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await req.json();
const post = await db.post.update({ where: { id }, data: body });
return NextResponse.json(post);
}DELETE handler returning 204
Returning a 204 No Content response is the correct HTTP pattern for a successful delete with no body.
// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.item.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
Discussion