Next.js API Routes
Answer
API Routes let you create API endpoints directly within your Next.js application, eliminating the need for a separate backend server for simple APIs.
Pages Router API Routes
// pages/api/hello.js → /api/hello
export default function handler(req, res) {
res.status(200).json({ message: 'Hello World' });
}
// With method handling
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ users: [] });
} else if (req.method === 'POST') {
const user = req.body;
// Create user
res.status(201).json(user);
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
App Router Route Handlers
// app/api/hello/route.js → /api/hello
export async function GET(request) {
return Response.json({ message: "Hello World" });
}
export async function POST(request) {
const body = await request.json();
return Response.json(body, { status: 201 });
}
// Other methods: PUT, DELETE, PATCH, HEAD, OPTIONS
Request Object
// PAGES ROUTER
export default function handler(req, res) {
const { query, cookies, headers, body } = req;
const { id } = query;
}
// APP ROUTER
export async function GET(request) {
const searchParams = request.nextUrl.searchParams;
const id = searchParams.get("id");
const cookie = request.cookies.get("session");
const headerValue = request.headers.get("authorization");
}
Dynamic Routes
// PAGES ROUTER
// pages/api/users/[id].js
export default function handler(req, res) {
const { id } = req.query;
res.json({ id });
}
// APP ROUTER
// app/api/users/[id]/route.js
export async function GET(request, { params }) {
const { id } = params;
return Response.json({ id });
}
Catch-All Routes
// PAGES: pages/api/[...slug].js
// URL: /api/a/b/c → req.query.slug = ['a', 'b', 'c']
// APP: app/api/[...slug]/route.js
export async function GET(request, { params }) {
const { slug } = params; // ['a', 'b', 'c']
return Response.json({ slug });
}
Headers and Cookies
// APP ROUTER
import { headers, cookies } from "next/headers";
export async function GET() {
const headersList = headers();
const cookieStore = cookies();
const auth = headersList.get("authorization");
const session = cookieStore.get("session");
// Set cookies
cookies().set("name", "value", { httpOnly: true });
return new Response("OK");
}
// Response with custom headers
export async function GET() {
return new Response("OK", {
headers: {
"Content-Type": "application/json",
"Cache-Control": "max-age=60",
},
});
}
Error Handling
// Pages Router
export default async function handler(req, res) {
try {
const data = await fetchData();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: "Internal Server Error" });
}
}
// App Router
export async function GET() {
try {
const data = await fetchData();
return Response.json(data);
} catch (error) {
return Response.json({ error: "Internal Server Error" }, { status: 500 });
}
}
Streaming Response
// App Router supports streaming
export async function GET() {
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(`Data ${i}\n`);
await new Promise((r) => setTimeout(r, 1000));
}
controller.close();
},
});
return new Response(stream, {
headers: { "Content-Type": "text/plain" },
});
}
CORS Configuration
// App Router
export async function GET() {
return Response.json(
{ data: "value" },
{
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
},
}
);
}
// Handle preflight
export async function OPTIONS() {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
Common Patterns
// POST with form data
export async function POST(request) {
const formData = await request.formData();
const name = formData.get("name");
return Response.json({ name });
}
// File upload handling
export async function POST(request) {
const formData = await request.formData();
const file = formData.get("file");
const bytes = await file.arrayBuffer();
// Save bytes to storage
}
// Protected route
export async function GET(request) {
const token = request.headers.get("authorization")?.split(" ")[1];
if (!token || !validateToken(token)) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
return Response.json({ data: "protected" });
}
Key Points
- Pages Router:
pages/api/with handler function - App Router:
app/api/*/route.jswith method exports - Dynamic routes work with
[param]syntax - App Router uses Web standard Request/Response
- No need for separate backend for simple APIs
- Can access cookies, headers, and body