Technology · Next.js
Next.js API Routes
Build and handle API endpoints directly inside your Next.js application.
TL;DR
- 01Create API routes in app/api folder with route.ts files.
- 02Export handler functions for HTTP methods (GET, POST, etc).
- 03Access request data and return typed Response objects with status codes.
Basic API Route
- Create API routes in app/api folder.
// app/api/hello/route.ts export async function GET(request: Request) { return Response.json({ message: 'Hello' }); } - Each route.ts file becomes an API endpoint.
- /app/api/hello/route.ts becomes /api/hello.
- Return a plain text response using the Response constructor.
export async function GET() { return new Response("Hello, world!", { headers: { "Content-Type": "text/plain" } }); } - Set response status and headers with the second argument.
export async function POST() { return Response.json({ created: true }, { status: 201 }); }
HTTP Methods
- Handle different HTTP methods with exports.
// app/api/posts/route.ts export async function GET() { const posts = await fetchPosts(); return Response.json(posts); } export async function POST(request: Request) { const body = await request.json(); const post = await createPost(body); return Response.json(post, { status: 201 }); } - Supports GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
- Handle PUT requests to update an existing resource.
export async function PUT(request: Request) { const body = await request.json(); const updated = await updatePost(body.id, body); return Response.json(updated); } - Handle DELETE to remove a resource by ID.
export async function DELETE(request: Request) { const { id } = await request.json(); await deletePost(id); return Response.json({ deleted: true }); } - Return 405 for unsupported methods.
export async function PATCH() { return Response.json({ error: "Method not allowed" }, { status: 405 }); }
Request Handling
- Read JSON body from requests.
export async function POST(request: Request) { const body = await request.json(); console.log(body); return Response.json({ success: true }); } - Access query parameters and headers.
export async function GET(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id'); const auth = request.headers.get('authorization'); return Response.json({ id, auth }); } - Parse form data from multipart requests.
export async function POST(request: Request) { const formData = await request.formData(); const name = formData.get("name") as string; return Response.json({ name }); } - Read cookies from incoming requests.
import { cookies } from "next/headers"; export async function GET() { const token = cookies().get("token")?.value; return Response.json({ token }); } - Set cookies in the response.
import { cookies } from "next/headers"; export async function POST() { cookies().set("session", "abc123", { httpOnly: true }); return Response.json({ ok: true }); }
Dynamic Routes
- Create dynamic API routes with [id].
// app/api/users/[id]/route.ts export async function GET( request: Request, { params }: { params: { id: string } } ) { const user = await fetchUser(params.id); return Response.json(user); } - Access route parameters from params object.
- Handle DELETE for a specific resource by ID.
export async function DELETE( request: Request, { params }: { params: { id: string } } ) { await deleteUser(params.id); return Response.json({ deleted: params.id }); } - Return 404 if the resource does not exist.
const user = await fetchUser(params.id); if (!user) { return Response.json({ error: "Not found" }, { status: 404 }); } return Response.json(user); - Use catch-all segments for nested dynamic APIs.
// app/api/docs/[...slug]/route.ts export async function GET( _req: Request, { params }: { params: { slug: string[] } } ) { return Response.json({ path: params.slug }); }
Error Handling and Status Codes
- Return appropriate status codes.
export async function GET(request: Request) { try { const data = await fetchData(); return Response.json(data); } catch (error) { return Response.json( { error: 'Failed to fetch data' }, { status: 500 } ); } } - Use Response.json with status and headers.
return Response.json(data, { status: 201, headers: { 'Content-Type': 'application/json' } }); - Return 400 for invalid or missing request body fields.
export async function POST(request: Request) { const body = await request.json(); if (!body.name) { return Response.json({ error: "Name is required" }, { status: 400 }); } return Response.json({ ok: true }); } - Return 401 for unauthenticated requests.
const token = request.headers.get("authorization"); if (!token) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - Log errors server-side before returning a generic message.
} catch (error) { console.error("API error:", error); return Response.json({ error: "Internal server error" }, { status: 500 }); }
Tip: Use "use server" functions in app/actions for simple mutations — API routes are for complex endpoints.
Warning: API routes are public by default — add authentication checks for sensitive endpoints.