Route handlers: your own API
A route handler is a route.ts file that answers HTTP requests with data instead of a page. This is how you build an API in Next.js: for a mobile app, for other sites or for requests from client components. For forms on your own site, server actions are usually enough.
The first handler
Create app/api/hello/route.ts:
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: 'Hello from the API' });
}The name of the exported function is the HTTP method. Open http://localhost:3000/api/hello — the browser shows {"message":"Hello from the API"}. Response is a standard web API; the Response.json method turns an object into JSON.
Comment data
Comments are stored the same way as posts, in memory for now. Create lib/comments.ts:
// lib/comments.ts
export type Comment = { id: number; postSlug: string; author: string; text: string };
const comments: Comment[] = [
{ id: 1, postSlug: 'hello-nextjs', author: 'Oleg', text: 'Great post!' },
];
export async function getComments(postSlug: string): Promise<Comment[]> {
return comments.filter((comment) => comment.postSlug === postSlug);
}
export async function addComment(postSlug: string, author: string, text: string): Promise<Comment> {
const comment = { id: comments.length + 1, postSlug, author, text };
comments.push(comment);
return comment;
}The comments API
The handler for /api/posts/hello-nextjs/comments lives in a folder with a dynamic segment:
// app/api/posts/[slug]/comments/route.ts
import { addComment, getComments } from '@/lib/comments';
type Context = { params: Promise<{ slug: string }> };
export async function GET(request: Request, { params }: Context) {
const { slug } = await params;
return Response.json(await getComments(slug));
}
export async function POST(request: Request, { params }: Context) {
const { slug } = await params;
const data = await request.json();
if (typeof data.text !== 'string' || data.text.trim() === '') {
return Response.json({ error: 'Empty comment' }, { status: 400 });
}
const comment = await addComment(slug, String(data.author ?? 'Guest'), data.text);
return Response.json(comment, { status: 201 });
}Address parameters arrive as the second argument, as with pages — as a promise. request.json() reads the request body.
Trying it
curl http://localhost:3000/api/posts/hello-nextjs/comments
curl -X POST http://localhost:3000/api/posts/hello-nextjs/comments -H "Content-Type: application/json" -d "{\"author\":\"Anna\",\"text\":\"Thanks\"}"The first command prints an array with Oleg's comment, the second adds a new one and returns it with status 201. Another GET shows both.
What else is available
| Task | How |
|---|---|
Parameters like ?page=2 | new URL(request.url).searchParams.get('page') |
| Headers | request.headers.get('authorization') |
| Other methods | PUT, PATCH, DELETE functions |
In Next.js 16, GET handlers are not cached by default and run on every request (unless Cache Components are turned on).
Common mistake
Putting route.ts and page.tsx in the same folder, such as app/posts. One address cannot be both a page and an API — Next.js reports a conflict. Keep the API in a separate app/api/ folder.