Technology · Next.js

Next.js API Routes Patterns

Build REST APIs with route handlers, middleware, CORS, and auth patterns.

TL;DR
  1. 01Create API routes in app/api folder for backend endpoints.
  2. 02Use request and response objects to handle HTTP methods.
  3. 03Set up CORS headers to allow safe cross-origin API requests.

Basic Route Handlers

  • Create a GET endpoint in app/api folder.
    // app/api/users/route.ts
    export async function GET(request: Request) {
      return Response.json({ users: [] });
    }
  • Handle different HTTP methods in the same file.
    export async function GET(request: Request) {
      return Response.json({ method: "GET" });
    }
    
    export async function POST(request: Request) {
      const data = await request.json();
      return Response.json({ created: data });
    }
  • Route handlers receive a Request and return a Response.
  • Use Response.json() for JSON responses.
  • Each function name corresponds to an HTTP method.

Dynamic Routes

  • Create dynamic endpoints with bracket syntax.
    // app/api/users/[id]/route.ts
    export async function GET(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      return Response.json({ userId: params.id });
    }
  • Access URL parameters through the params object.
  • Use the same pattern for POST, PUT, DELETE, etc.
    export async function PUT(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      const data = await request.json();
      return Response.json({ updated: params.id });
    }
  • Return 404 when the resource doesn't exist.
    export async function GET(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      const user = await getUser(params.id);
      if (!user) {
        return Response.json({ error: "Not found" }, { status: 404 });
      }
      return Response.json(user);
    }
  • Use catch-all segments for deeply nested dynamic routes.
    // app/api/docs/[...path]/route.ts
    export async function GET(
      _req: Request,
      { params }: { params: { path: string[] } }
    ) {
      return Response.json({ segments: params.path });
    }

Request Body Parsing

  • Parse JSON from the request body.
    export async function POST(request: Request) {
      const data = await request.json();
      console.log(data); // { name: "Alice" }
      return Response.json({ success: true });
    }
  • Handle form data for multipart uploads.
    export async function POST(request: Request) {
      const formData = await request.formData();
      const file = formData.get("file");
      return Response.json({ uploaded: true });
    }
  • Parse query parameters from the URL.
    export async function GET(request: Request) {
      const { searchParams } = new URL(request.url);
      const query = searchParams.get("q");
      return Response.json({ query });
    }

CORS and Headers

  • Set CORS headers for cross-origin requests.
    export async function GET(request: Request) {
      const response = Response.json({ data: "hello" });
      response.headers.set("Access-Control-Allow-Origin", "*");
      response.headers.set("Access-Control-Allow-Methods", "GET, POST");
      return response;
    }
  • Use middleware for consistent header handling.
    // middleware.ts
    export function middleware(request: Request) {
      const response = Response.next();
      response.headers.set("Access-Control-Allow-Origin", "*");
      return response;
    }
  • Return OPTIONS response for preflight requests.
    export async function OPTIONS() {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE"
        }
      });
    }

Common Patterns

  • Database queries in API routes.
    import { prisma } from "@/lib/prisma";
    
    export async function GET() {
      const users = await prisma.user.findMany();
      return Response.json(users);
    }
  • Error handling with status codes.
    export async function POST(request: Request) {
      try {
        const data = await request.json();
        if (!data.name) {
          return Response.json(
            { error: "Name required" },
            { status: 400 }
          );
        }
        return Response.json({ success: true });
      } catch (error) {
        return Response.json(
          { error: "Invalid request" },
          { status: 500 }
        );
      }
    }
  • Authentication in API routes.
    export async function GET(request: Request) {
      const token = request.headers.get("authorization");
      if (!token) {
        return Response.json({ error: "Unauthorized" }, { status: 401 });
      }
      return Response.json({ data: "protected" });
    }

Tip: Keep API logic separate by using helper functions and middleware to avoid duplicating common concerns like auth and CORS across routes.

Warning: Never expose secrets or sensitive data in API responses — always validate input and use proper authentication before returning user data.

Next.js Analytics and MonitoringNext.js API Routes