Technology · Next.js

Next.js API Routes

Build and handle API endpoints directly inside your Next.js application.

TL;DR
  1. 01Create API routes in app/api folder with route.ts files.
  2. 02Export handler functions for HTTP methods (GET, POST, etc).
  3. 03Access request data and return typed Response objects with status codes.

Basic API Route

  • Create API routes in app/api folder.
    // app/api/hello/route.ts
    export async function GET(request: Request) {
      return Response.json({ message: 'Hello' });
    }
  • Each route.ts file becomes an API endpoint.
  • /app/api/hello/route.ts becomes /api/hello.
  • Return a plain text response using the Response constructor.
    export async function GET() {
      return new Response("Hello, world!", {
        headers: { "Content-Type": "text/plain" }
      });
    }
  • Set response status and headers with the second argument.
    export async function POST() {
      return Response.json({ created: true }, { status: 201 });
    }

HTTP Methods

  • Handle different HTTP methods with exports.
    // 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 });
    }
  • Supports GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
  • Handle PUT requests to update an existing resource.
    export async function PUT(request: Request) {
      const body = await request.json();
      const updated = await updatePost(body.id, body);
      return Response.json(updated);
    }
  • Handle DELETE to remove a resource by ID.
    export async function DELETE(request: Request) {
      const { id } = await request.json();
      await deletePost(id);
      return Response.json({ deleted: true });
    }
  • Return 405 for unsupported methods.
    export async function PATCH() {
      return Response.json({ error: "Method not allowed" }, { status: 405 });
    }

Request Handling

  • Read JSON body from requests.
    export async function POST(request: Request) {
      const body = await request.json();
      console.log(body);
      return Response.json({ success: true });
    }
  • Access query parameters and headers.
    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 });
    }
  • Parse form data from multipart requests.
    export async function POST(request: Request) {
      const formData = await request.formData();
      const name = formData.get("name") as string;
      return Response.json({ name });
    }
  • Read cookies from incoming requests.
    import { cookies } from "next/headers";
    
    export async function GET() {
      const token = cookies().get("token")?.value;
      return Response.json({ token });
    }
  • Set cookies in the response.
    import { cookies } from "next/headers";
    
    export async function POST() {
      cookies().set("session", "abc123", { httpOnly: true });
      return Response.json({ ok: true });
    }

Dynamic Routes

  • Create dynamic API routes with [id].
    // app/api/users/[id]/route.ts
    export async function GET(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      const user = await fetchUser(params.id);
      return Response.json(user);
    }
  • Access route parameters from params object.
  • Handle DELETE for a specific resource by ID.
    export async function DELETE(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      await deleteUser(params.id);
      return Response.json({ deleted: params.id });
    }
  • Return 404 if the resource does not exist.
    const user = await fetchUser(params.id);
    if (!user) {
      return Response.json({ error: "Not found" }, { status: 404 });
    }
    return Response.json(user);
  • Use catch-all segments for nested dynamic APIs.
    // app/api/docs/[...slug]/route.ts
    export async function GET(
      _req: Request,
      { params }: { params: { slug: string[] } }
    ) {
      return Response.json({ path: params.slug });
    }

Error Handling and Status Codes

  • Return appropriate status codes.
    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 }
        );
      }
    }
  • Use Response.json with status and headers.
    return Response.json(data, {
      status: 201,
      headers: { 'Content-Type': 'application/json' }
    });
  • Return 400 for invalid or missing request body fields.
    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 });
    }
  • Return 401 for unauthenticated requests.
    const token = request.headers.get("authorization");
    if (!token) {
      return Response.json({ error: "Unauthorized" }, { status: 401 });
    }
  • Log errors server-side before returning a generic message.
    } catch (error) {
      console.error("API error:", error);
      return Response.json({ error: "Internal server error" }, { status: 500 });
    }

Tip: Use "use server" functions in app/actions for simple mutations — API routes are for complex endpoints.

Warning: API routes are public by default — add authentication checks for sensitive endpoints.

Next.js API Routes PatternsNext.js App Router