Next.js cookbook · Beginner

Build a validated JSON API with Route Handlers

Expose a REST endpoint from a Next.js app for a mobile client, with auth and input validation.

Approach

Create app/api/<resource>/route.ts exporting GET/POST functions. Authenticate from the Authorization header or session, validate JSON with Zod, return proper status codes with Response.json(), and keep business logic in shared server modules (also used by Server Components/Actions).

Solution

// app/api/projects/route.ts
import { z } from "zod";
import { type NextRequest } from "next/server";

const CreateProject = z.object({ name: z.string().trim().min(1).max(100), color: z.string().regex(/^#[0-9a-f]{6}$/i).optional() });

export async function GET(request: NextRequest) {
  const user = await authenticate(request);
  if (!user) return Response.json({ title: "Unauthorized" }, { status: 401 });
  const limit = Math.min(Number(request.nextUrl.searchParams.get("limit")) || 20, 100);
  const projects = await projectsService.list(user.id, limit);
  return Response.json({ items: projects });
}

export async function POST(request: NextRequest) {
  const user = await authenticate(request);
  if (!user) return Response.json({ title: "Unauthorized" }, { status: 401 });
  const body = await request.json().catch(() => null);
  const parsed = CreateProject.safeParse(body);
  if (!parsed.success) {
    return Response.json({ title: "Validation failed", errors: parsed.error.flatten().fieldErrors }, { status: 422 });
  }
  const project = await projectsService.create(user.id, parsed.data);
  return Response.json(project, { status: 201, headers: { Location: `/api/projects/${project.id}` } });
}

// app/api/projects/[id]/route.ts — params are async in Next.js 15+
export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  // ...
}

Pitfalls

  • Calling your own Route Handlers from Server Components via HTTP (call the shared function directly).
  • Forgetting CORS headers/OPTIONS for cross-origin clients.
  • Unbounded limit parameters.

Variations

  • tRPC or oRPC for typed APIs consumed by your own clients.
  • A separate backend service when the API is large or shared by many apps.

Further reading: Next.js: Route Handlers

Reference: https://nextjs.org/docs/app/getting-started/route-handlers

More Next.js recipes