patterntypescriptnextjsModerate
API Route migration from Pages Router to App Router Route Handlers
Viewed 0 times
Next.js 13+ for Route Handlers
API route migrationroute handlersreq.bodyreq.queryres.jsonpages to app router
Problem
Migrating pages/api/*.ts to app/api/ requires rewriting request/response handling from Node.js http style to Web Fetch API. Patterns like req.body, req.query, res.json(), and res.status() don't exist in Route Handlers.
Solution
Translate Pages Router API patterns to Route Handler equivalents:
// PAGES ROUTER: pages/api/users/[id].ts
export default async function handler(req, res) {
const { id } = req.query;
if (req.method === 'GET') {
const user = await db.user.findUnique({ where: { id: String(id) } });
if (!user) return res.status(404).json({ error: 'Not found' });
return res.status(200).json(user);
}
if (req.method === 'PUT') {
const body = req.body; // pre-parsed by Next.js
const user = await db.user.update({ where: { id: String(id) }, data: body });
return res.status(200).json(user);
}
res.setHeader('Allow', ['GET', 'PUT']);
res.status(405).end();
}
// APP ROUTER: app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await db.user.findUnique({ where: { id } });
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json(user);
}
export async function PUT(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await req.json(); // must manually parse
const user = await db.user.update({ where: { id }, data: body });
return NextResponse.json(user);
}
// PAGES ROUTER: pages/api/users/[id].ts
export default async function handler(req, res) {
const { id } = req.query;
if (req.method === 'GET') {
const user = await db.user.findUnique({ where: { id: String(id) } });
if (!user) return res.status(404).json({ error: 'Not found' });
return res.status(200).json(user);
}
if (req.method === 'PUT') {
const body = req.body; // pre-parsed by Next.js
const user = await db.user.update({ where: { id: String(id) }, data: body });
return res.status(200).json(user);
}
res.setHeader('Allow', ['GET', 'PUT']);
res.status(405).end();
}
// APP ROUTER: app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const user = await db.user.findUnique({ where: { id } });
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json(user);
}
export async function PUT(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await req.json(); // must manually parse
const user = await db.user.update({ where: { id }, data: body });
return NextResponse.json(user);
}
Why
Route Handlers use the Web Fetch API (Request/Response) instead of Node.js http types. Method routing is done with named exports (GET, POST, PUT, DELETE) instead of req.method switching. params is now a Promise in Next.js 15.
Gotchas
- req.body is not auto-parsed — use await req.json() for JSON or req.formData() for multipart
- req.query becomes new URL(req.url).searchParams in Route Handlers
- Method not allowed is automatic — undefined exports return 405 without any code
- CORS headers can be added via response headers or via next.config.js headers()
Code Snippets
Pages Router to Route Handler API equivalents
// Pages: const { id } = req.query;
// App Router equivalent:
const { searchParams } = new URL(req.url);
const id = searchParams.get('id');
// Pages: const body = req.body;
// App Router equivalent:
const body = await req.json();Context
When migrating Next.js Pages Router API routes to App Router Route Handlers
Revisions (0)
No revisions yet.