Build and handle API endpoints directly inside your Next.js application.
// app/api/hello/route.ts
export async function GET(request: Request) {
return Response.json({ message: 'Hello' });
}export async function GET() {
return new Response("Hello, world!", {
headers: { "Content-Type": "text/plain" }
});
}export async function POST() {
return Response.json({ created: true }, { status: 201 });
}// app/api/posts/route.ts
export async function GET() {
const posts = await fetchPosts();
return Response.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return Response.json(post, { status: 201 });
}export async function PUT(request: Request) {
const body = await request.json();
const updated = await updatePost(body.id, body);
return Response.json(updated);
}export async function DELETE(request: Request) {
const { id } = await request.json();
await deletePost(id);
return Response.json({ deleted: true });
}export async function PATCH() {
return Response.json({ error: "Method not allowed" }, { status: 405 });
}export async function POST(request: Request) {
const body = await request.json();
console.log(body);
return Response.json({ success: true });
}export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const auth = request.headers.get('authorization');
return Response.json({ id, auth });
}export async function POST(request: Request) {
const formData = await request.formData();
const name = formData.get("name") as string;
return Response.json({ name });
}export async function GET() {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("chunk 1"));
controller.close();
}
});
return new Response(stream);
}const { name, email } = await request.json();
if (!name || !email) {
return Response.json({ error: "name and email are required" }, { status: 400 });
}cookies() is async in Next.js 15.import { cookies } from "next/headers";
export async function GET() {
const cookieStore = await cookies(); // must await in Next.js 15
const token = cookieStore.get("token")?.value;
return Response.json({ token });
}import { cookies } from "next/headers";
export async function POST() {
const cookieStore = await cookies();
cookieStore.set("session", "abc123", { httpOnly: true });
return Response.json({ ok: true });
}export async function GET(request: Request) {
const auth = request.headers.get("authorization");
const contentType = request.headers.get("content-type");
return Response.json({ auth, contentType });
}export async function GET() {
return new Response(JSON.stringify({ ok: true }), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store"
}
});
}next/headers headers() to read request headers server-side (also async in Next.js 15).import { headers } from "next/headers";
const headersList = await headers();
const userAgent = headersList.get("user-agent");export async function GET(request: Request) {
try {
const data = await fetchData();
return Response.json(data);
} catch (error) {
return Response.json(
{ error: 'Failed to fetch data' },
{ status: 500 }
);
}
}return Response.json(data, {
status: 201,
headers: { 'Content-Type': 'application/json' }
});export async function POST(request: Request) {
const body = await request.json();
if (!body.name) {
return Response.json({ error: "Name is required" }, { status: 400 });
}
return Response.json({ ok: true });
}const token = request.headers.get("authorization");
if (!token) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}} catch (error) {
console.error("API error:", error);
return Response.json({ error: "Internal server error" }, { status: 500 });
}Create a file at app/api/[route]/route.ts and export named async functions matching HTTP methods (GET, POST, PUT, DELETE). Each function receives a Request object and must return a Response, e.g. return Response.json({ data }) or new Response(body, { status: 201 }).
Use new URL(request.url).searchParams to access query parameters, and await request.json() to parse a JSON body. For form data, use await request.formData() instead.
Name your folder with brackets, e.g. app/api/users/[id]/route.ts, then access the param via the second argument: export async function GET(request, { params }) { const { id } = await params; }.
Use Server Actions ("use server") for form submissions and simple data mutations tied to UI components — they require less boilerplate. Use API routes when you need a public HTTP endpoint, webhook receiver, or need full control over headers, status codes, and response shape.
Pass a status option to the Response constructor or Response.json(): return Response.json({ error: 'Not found' }, { status: 404 }). For errors, always set an explicit status code — omitting it defaults to 200 even on failure.