
Next.js App Router changed how we build APIs. In this guide, you'll learn the right way to build REST APIs in 2026 - with real code, folder structure, and production tips.
"Can I build a REST API with Next.js App Router?" Yes, and you do not need a separate backend to do it. Next.js gives you everything you need to build a clean, production-ready API right inside your app.
"Can I build a REST API with Next.js App Router?" Yes, and you do not need a separate backend to do it. Next.js gives you everything you need to build a clean, production-ready API right inside your app.
In this guide I will show you the right way to do it in 2026: the folder structure, working code for every method, validation, CORS, and the mistakes that quietly break APIs in production. This is the same approach I use on real client projects.
What changed with the App Router
The App Router introduced Route Handlers, which replace the older pages/api approach. You now define your API inside the app directory using a route.ts file, and you export a function for each HTTP method you want to support (GET, POST, PUT, DELETE). It is cleaner, more explicit, and built on the standard Web Request and Response objects.
Step 1: Folder structure
Keep your API organized and predictable. A clean structure scales far better than one giant file:
app/
api/
users/
route.ts // handles /api/users (GET all, POST new)
[id]/
route.ts // handles /api/users/:id (GET one, PUT, DELETE)Each route.ts owns one resource. This mirrors how REST is meant to work and keeps everything easy to find.
Step 2: A GET endpoint
Inside app/api/users/route.ts, export a GET function that returns JSON:
import { NextResponse } from "next/server";
export async function GET() {
const users = await db.user.findMany();
return NextResponse.json(users);
}
NextResponse.json handles serialization and sets the correct content-type header for you.
Step 3: A POST endpoint with validation
For creating data, read the request body and never trust it blindly. Validate first:
export async function POST(req: Request) {
const body = await req.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}Notice the 201 status code for a successful creation. Returning the right status codes is part of building a proper REST API, not an optional nicety.
Step 4: Dynamic routes for a single resource
In app/api/users/[id]/route.ts, you access the id from params. In Next.js 15, params is async, so you await it:
import { NextResponse } from "next/server";
export async function GET(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await db.user.findUnique({ where: { id } });
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
return NextResponse.json(user);
}
Always handle the "not found" case with a 404. Returning an empty 200 for missing data is a classic bug that confuses every client that consumes your API.
Step 5: Real validation with Zod
Hand-checking fields gets messy fast. A schema library like Zod makes validation clean and safe:
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
export async function POST(req: Request) {
const body = await req.json();
const result = userSchema.safeParse(body);
if (!result.success) {
return NextResponse.json({ error: result.error.flatten() }, { status: 400 });
}
const user = await db.user.create({ data: result.data });
return NextResponse.json(user, { status: 201 });
}
Now bad input gets a clean 400 with a helpful message instead of crashing your handler.
Step 6: CORS and headers
If a browser app on another domain will call your API, you need CORS headers. You can set them per response, or centralize them in middleware for every API route. At minimum, allow the origins and methods you actually use, and nothing more. Being specific here is a small security win.
Common mistakes to avoid
- Mixing the old pages/api and the new Route Handlers in one project. Pick one, and for new work that is the App Router.
- Skipping proper status codes. 200, 201, 400, 404, and 500 all mean something. Use them.
- No error handling. Wrap risky work and return a clean 500 instead of leaking a stack trace.
- Trusting input. Always validate the body before it touches your database.
What to build next
Once your API works, the natural next steps are connecting a real database with Prisma and PostgreSQL, adding authentication, and deploying. I cover the database and architecture side in depth in my guide on building multi-tenant SaaS with Next.js, Prisma, and PostgreSQL, and the go-live side in deploying a Next.js and PostgreSQL app on a VPS.
Frequently asked questions
Can you build a REST API with Next.js?
Yes. The Next.js App Router lets you build a full REST API using Route Handlers (route.ts files) inside the app directory, with no separate backend server needed. It supports all HTTP methods.
What are Route Handlers in Next.js?
Route Handlers are the App Router's way of defining API endpoints. You create a route.ts file and export a function per HTTP method (GET, POST, PUT, DELETE), built on the standard Web Request and Response objects.
How do I validate request data in a Next.js API?
Use a schema validation library like Zod. Define a schema, run safeParse on the request body, and return a 400 response if validation fails, so invalid input never reaches your database.
How do I handle dynamic route parameters in Next.js 15?
In Next.js 15, the params object is a Promise in Route Handlers, so you await it: await params gives you the values, for example the id in app/api/users/[id]/route.ts.
Need a production API or a full SaaS backend built the right way, with proper validation, error handling, and structure? That is exactly what I do. Tell me what you are building (https://osamahabib.com/contact) and I will help you ship it, or explore my SaaS development services.
In this guide I will show you the right way to do it in 2026: the folder structure, working code for every method, validation, CORS, and the mistakes that quietly break APIs in production. This is the same approach I use on real client projects.
What changed with the App Router
The App Router introduced Route Handlers, which replace the older pages/api approach. You now define your API inside the app directory using a route.ts file, and you export a function for each HTTP method you want to support (GET, POST, PUT, DELETE). It is cleaner, more explicit, and built on the standard Web Request and Response objects.
Osama Habib
Multan, Pakistan
Full Stack Developer specialising in Next.js, Node.js, and the MERN stack. I write about modern web development, system design, and practical engineering.


