API
How to add and customize API route handlers with typed responses, structured logging, and error patterns.
1. Existing Endpoints
Both apps include health check endpoints and data APIs out of the box.
| App | Endpoint | Description |
|---|---|---|
| Website | GET /api/health | Health check |
| Website | GET /api/posts?tag=X&limit=N | Blog posts with optional filtering |
| Case Study | GET /api/health | Health check |
| Case Study | GET /api/catalog?category=X&subcategory=Y | Catalog items with optional filtering |
2. Adding a New Route
Create a new file at src/app/api/{name}/route.ts. Use NextRequest and NextResponse for typed request/response handling. Always use the structured logger instead of console.log.
1 // src/app/api/users/route.ts 2 import { NextRequest, NextResponse } from "next/server"; 3 import { logger } from "@/server/logging/logger"; 4 5 export async function GET(request: NextRequest) { 6 const { searchParams } = request.nextUrl; 7 const role = searchParams.get("role"); 8 9 logger.info("Fetching users", { role }); 10 11 if (!role) { 12 return NextResponse.json( 13 { error: "Missing role parameter" }, 14 { status: 400 }, 15 ); 16 } 17 18 const users = [{ id: 1, name: "Erik", role: "admin" }]; 19 20 return NextResponse.json({ data: users }); 21 }
3. Handling POST Requests
Parse the request body with request.json(). Return typed error responses with appropriate status codes. Log all operations for observability.
1 export async function POST(request: NextRequest) { 2 try { 3 const body = await request.json(); 4 5 logger.info("Creating resource", { body }); 6 7 // Validate required fields 8 if (!body.name) { 9 return NextResponse.json( 10 { error: "Name is required" }, 11 { status: 400 }, 12 ); 13 } 14 15 const created = { id: Date.now(), ...body }; 16 17 return NextResponse.json({ data: created }, { status: 201 }); 18 } catch { 19 logger.error("Failed to create resource"); 20 return NextResponse.json( 21 { error: "Internal server error" }, 22 { status: 500 }, 23 ); 24 } 25 }
4. Proxying to a Backend
When using a separate backend (FeatherJS, FastAPI), Next.js API routes act as a proxy. This keeps the backend URL private and lets you add auth headers server-side.
1 // src/app/api/projects/route.ts 2 export async function GET(request: NextRequest) { 3 const token = request.cookies.get("token")?.value; 4 5 const res = await fetch(`${process.env.BACKEND_URL}/projects`, { 6 headers: { 7 Authorization: `Bearer ${token}`, 8 "Content-Type": "application/json", 9 }, 10 }); 11 12 if (!res.ok) { 13 const error = await res.json(); 14 return NextResponse.json( 15 { error: error.message }, 16 { status: res.status }, 17 ); 18 } 19 20 const data = await res.json(); 21 return NextResponse.json({ data }); 22 }
Conventions: Use logger.info() for all operations, logger.error() for failures. Return { error: "message" } for errors and { data: ... } for success. Always validate input at the boundary.