If you’re building on Vercel’s v0, mastering the Next.js App Router is essential. This guide walks you step by step through building a REST API using route.ts
files, structured folders, typed validation, and middleware for authentication — everything you need for scalable backend APIs.
1. Introduction: Embracing the Next.js App Router for REST API Development
With the release of Next.js 13, the App Router replaces the older pages/api
system. Now you can colocate backend API logic directly with frontend components in the app/
directory — streamlining development for modern full-stack apps.
You’ll define route handlers in route.ts
files, enabling clear HTTP method logic while staying inside your app’s file structure.
2. Setting Up Your Project with the App Router
Start by generating your project:
npx create-next-app@latest my-v0-api
Enable:
- TypeScript
- App Router
Recommended structure:
app/
api/
v0/
users/
route.ts
_utils/
_models/
_services/
_middleware/
This approach cleanly separates routing, logic, types, and helpers.
3. Writing API Endpoints with Route Handlers
Each REST endpoint is defined in a route.ts
file, using exported async functions named after HTTP methods:
Basic GET Example
export async function GET() {
return new Response(JSON.stringify({ message: 'Welcome to v0' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
Supported methods: GET
, POST
, PUT
, DELETE
, PATCH
, OPTIONS
, HEAD
. Use request.nextUrl.searchParams
to access query strings and request.json()
for reading the request body.
4. Data Validation with TypeScript and Zod
To keep your API safe and clean, combine TypeScript with a runtime validation library like Zod:
const schema = z.object({ name: z.string().min(2), email: z.string().email() });
export async function POST(request: Request) {
const data = schema.parse(await request.json());
return new Response(JSON.stringify(data), { status: 201 });
}
If validation fails, return a 400 with error details. You can also use Yup if preferred, though Zod integrates better with TS.
5. Adding Auth: NextAuth.js and Middleware
Use NextAuth.js for secure session-based authentication.
Example:
import { getServerSession } from 'next-auth';
export async function GET(request: Request) {
const session = await getServerSession();
if (!session) return new Response('Unauthorized', { status: 401 });
return new Response(JSON.stringify(session.user));
}
Add middleware for role-based restrictions:
export default async function middleware(req) {
const token = req.nextauth.token;
if (req.nextUrl.pathname.startsWith('/api/v0/admin') && token?.role !== 'admin') {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}
return NextResponse.next();
}
6. Versioning the API with /v0
Adopt URI-based versioning for your API endpoints:
app/api/v0/users/route.ts
This makes it easy to add /v1
later without breaking existing consumers. You can conditionally branch logic by version or separate it into distinct folders.
7. Best Practices for Production APIs
- Stateless logic — no reliance on sessions
- Pagination — support
limit
andoffset
or cursor-based queries - Proper HTTP codes — 200, 201, 400, 401, 404, 500
- Caching — use
revalidateTag
, CDN headers, and browser cache control - Async everything — always
await
DB and API calls
8. The Service Layer: Keep Logic Reusable
Move business logic into /_services
:
// route.ts
import { getUsers } from '_services/userService';
export async function GET() {
const users = await getUsers();
return new Response(JSON.stringify(users));
}
// _services/userService.ts
export async function getUsers() {
return db.user.findMany();
}
Benefits:
- Isolated logic, easy to test
- DRY — reuse across routes
- Better team collaboration
9. What Developers Recommend in 2025
- Prefer Route Handlers for APIs; use Server Actions for form submissions
- Type everything — use Zod or Yup for runtime validation
- Authenticate with NextAuth.js, authorize with Middleware
- Don’t skip await — async bugs are silent killers
- Always validate incoming data, even if the UI does too
FAQ
How do I version my API in Next.js?
Use folders like app/api/v0/resource
— it’s clear and scalable.
How do I secure an API route?
Authenticate with getServerSession()
and restrict paths using middleware.
Zod or Yup for validation?
Both work, but Zod plays better with TypeScript.
What’s the difference between Route Handlers and Server Actions?
Handlers = full HTTP APIs. Server Actions = form-specific, React-powered logic.
Learn More About Why Every v0 Project Needs a Service Layer (and How to Build It).