API Routes & Route Handlers
In the App Router, API endpoints are Route Handlers. Create a folder under app/api and export functions named GET, POST, PUT, PATCH, DELETE from route.ts.
app/api/.../route.ts
In the App Router, API endpoints are Route Handlers. Create a folder under app/api and export functions named GET, POST, PUT, PATCH, DELETE from route.ts.
Each file maps to a URL path. app/api/hello/route.ts → /api/hello.
Real-life example: A Route Handler is a small service window at the back of the shop — browsers and mobile apps knock on /api/... and get JSON, not a full HTML page.
- Export named HTTP methods from route.ts
- Use Request and Response (Web standard APIs)
- Prefer Server Actions for form mutations from your own UI; use Route Handlers for public APIs and webhooks
GET and POST examples
GET reads data. POST creates or triggers work. Always return the right status codes: 200/201 success, 400 bad input, 401 unauthorized, 404 missing, 500 server error.
Real-life example: GET is asking the library for a book list. POST is submitting a new book donation form.
import { NextRequest, NextResponse } from "next/server";
const courses = [
{ id: "1", title: "Next.js Zero to Full-Stack" },
{ id: "2", title: "React Hooks Deep Dive" },
];
export async function GET() {
return NextResponse.json(courses);
}
export async function POST(req: NextRequest) {
const body = await req.json();
const title = String(body.title ?? "").trim();
if (!title) {
return NextResponse.json(
{ error: "title is required" },
{ status: 400 }
);
}
const created = { id: String(courses.length + 1), title };
courses.push(created);
return NextResponse.json(created, { status: 201 });
}"use client";
import { useState } from "react";
export function AddCourseForm() {
const [title, setTitle] = useState("");
const [message, setMessage] = useState("");
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/courses", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title }),
});
const data = await res.json();
setMessage(res.ok ? `Saved: ${data.title}` : data.error);
}
return (
<form onSubmit={onSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Course title"
/>
<button type="submit">Add</button>
{message && <p>{message}</p>}
</form>
);
}Building and managing API routes
Group related endpoints under folders. Share helpers for auth checks and validation. Document expected JSON shapes for teammates.
For webhooks (Stripe, GitHub), verify signatures before trusting the body.
Real-life example: Managing API routes is organizing a filing cabinet — one drawer per topic (users, courses, webhooks), labeled clearly so nobody digs in the wrong place.
- app/api/users/route.ts — collection
- app/api/users/[id]/route.ts — one item
- app/api/webhooks/stripe/route.ts — external events
import { NextResponse } from "next/server";
export function jsonError(message: string, status = 400) {
return NextResponse.json({ error: message }, { status });
}
// usage: return jsonError("Unauthorized", 401);