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.

Tips

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

Warnings

  1. 01API routes are public by default — add authentication checks for sensitive endpoints.

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 authorization 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 });
    }
  • Stream a large response body using ReadableStream.
    export async function GET() {
      const stream = new ReadableStream({
        start(controller) {
          controller.enqueue(new TextEncoder().encode("chunk 1"));
          controller.close();
        }
      });
      return new Response(stream);
    }
  • Validate request body shape before processing.
    const { name, email } = await request.json();
    if (!name || !email) {
      return Response.json({ error: "name and email are required" }, { status: 400 });
    }

Cookies and Headers

  • Read cookies from incoming requests — 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 });
    }
  • Set cookies in the response.
    import { cookies } from "next/headers";
    
    export async function POST() {
      const cookieStore = await cookies();
      cookieStore.set("session", "abc123", { httpOnly: true });
      return Response.json({ ok: true });
    }
  • Read request headers directly from the Request object.
    export async function GET(request: Request) {
      const auth = request.headers.get("authorization");
      const contentType = request.headers.get("content-type");
      return Response.json({ auth, contentType });
    }
  • Set custom response headers.
    export async function GET() {
      return new Response(JSON.stringify({ ok: true }), {
        headers: {
          "Content-Type": "application/json",
          "Cache-Control": "no-store"
        }
      });
    }
  • Use 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");

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 });
    }

FAQ