Next.js Route Handlers
Web applications often need endpoints that return data instead of pages. A mobile app might ask for a product list. A payment provider might send a notification to your server. A form on another website might post a message to your system. Route Handlers give the App Router a clean way to build these endpoints with standard web request and response objects.
The Everyday View
A page works like a printed menu handed to a guest at the table. A Route Handler works like the kitchen service window. Waiters and delivery drivers pass in an order, and the kitchen returns a plain package of food. Nobody decorates the package. The data is the product.
Where Route Handlers Live
A Route Handler sits in a file named route.js or route.ts inside the app folder. The folder path becomes the URL.
app/
api/
hello/
route.js -> /api/hello
products/
route.js -> /api/products
[id]/
route.js -> /api/products/42
A folder segment cannot hold both a page.js and a route.js file. The two files would compete for the same URL, so Next.js rejects the setup.
Your First Handler
Export a function named after an HTTP method. Supported names include GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.
// app/api/hello/route.js
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ message: 'Hello from Next.js' });
}Open /api/hello in the browser. The page shows the JSON text {"message":"Hello from Next.js"}.
Request Flow
Client Route Handler Data source | GET /api/products | | |--------------------> | | | | query products | | |---------------------> | | | list of products | | | <---------------------| | JSON response | | | <--------------------| |
Reading Data From a Request
Query Strings
The request object carries the URL. Use nextUrl.searchParams to read values after the question mark.
export async function GET(request) {
const category = request.nextUrl.searchParams.get('category');
const items = await findProducts(category);
return NextResponse.json(items);
}A call to /api/products?category=books sets category to books.
JSON Bodies
A POST request usually carries a JSON body. Call request.json() to read it.
export async function POST(request) {
const body = await request.json();
if (!body.title) {
return NextResponse.json(
{ error: 'Title is required' },
{ status: 400 }
);
}
const created = await saveProduct(body);
return NextResponse.json(created, { status: 201 });
}The status code tells the caller what happened. Code 201 means a new item was created. Code 400 means the request contained a mistake.
Dynamic Segments
Square brackets in a folder name capture part of the URL. Recent Next.js versions deliver the captured values as a promise, so await them first.
// app/api/products/[id]/route.js
export async function GET(request, { params }) {
const { id } = await params;
const product = await findProductById(id);
if (!product) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(product);
}Common Status Codes
| Code | Meaning | Typical Use |
|---|---|---|
| 200 | OK | Successful read |
| 201 | Created | Successful create |
| 400 | Bad request | Missing or invalid input |
| 401 | Unauthorized | Visitor not signed in |
| 404 | Not found | Item does not exist |
| 500 | Server error | Unexpected failure |
Headers and Cookies
Read request headers with request.headers.get('authorization'). Attach response headers through the options object of NextResponse.json.
return NextResponse.json(data, {
headers: { 'Cache-Control': 'no-store' },
});Caching Behavior
Current Next.js versions treat GET handlers as dynamic by default, so each request runs the code again. Older versions cached some GET handlers automatically. Check the documentation for the version in your project before you rely on either behavior.
Route Handlers vs Server Actions
Server Actions suit form submissions and data changes triggered from your own pages. Route Handlers suit endpoints that outside clients call, such as mobile apps, webhooks, and third-party tools. Pick the tool that matches the caller.
Common Mistakes
Calling Your Own Handler From a Server Component
A Server Component can read the database directly. An extra network hop to your own endpoint adds delay and gains nothing.
Skipping Input Checks
Outside callers can send anything. Validate every field before saving.
Returning Raw Error Details
Stack traces help attackers. Log the details on the server and send a short message to the caller.
Quick Summary
Route Handlers turn a route.js file into an HTTP endpoint. Export one function per method, read input from the request, and return JSON with a clear status code.
