Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

TaskHow
Parameters like ?page=2new URL(request.url).searchParams.get('page')
Headersrequest.headers.get('authorization')
Other methodsPUT, 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.

Thanks for reading!

Donate to the site — we work hard for you.