Next.js API Routes

Build API endpoints in Next.js with route handlers that read requests and return JSON responses.

TL;DR

  1. Create an endpoint by exporting GET or POST from route.ts.
  2. Read a JSON request body with await request.json() in a handler.
  3. Return data with Response.json() and set a status code.

Creating Handlers

    GET

    Export a function named GET to respond to read requests at this path.

    // app/api/posts/route.ts
    export async function GET() {
      const posts = await getPosts();
      return Response.json(posts);
    }
    POST

    Read a JSON body and return a 201 status after creating a record.

    export async function POST(request: Request) {
      const body = await request.json();
      const post = await createPost(body);
      return Response.json(post, { status: 201 });
    }
    DELETE

    Export PUT, PATCH, or DELETE from the same file to handle more methods.

    export async function DELETE() {
      await clearPosts();
      return new Response(null, { status: 204 });
    }

Reading the Request

    searchParams

    Read query string values from the request URL with nextUrl.searchParams.

    import type { NextRequest } from 'next/server';
    
    export async function GET(request: NextRequest) {
      const q = request.nextUrl.searchParams.get('q');
      return Response.json({ q });
    }
    params

    Await the dynamic segment values from the second argument of the handler.

    // app/api/posts/[id]/route.ts
    export async function GET(
      _req: Request,
      ctx: RouteContext<'/api/posts/[id]'>
    ) {
      const { id } = await ctx.params;
      return Response.json({ id });
    }
    cookies() and headers()

    Read cookies and headers with the async helpers from next/headers.

    import { cookies, headers } from 'next/headers';
    
    export async function GET() {
      const token = (await cookies()).get('token');
      const agent = (await headers()).get('user-agent');
      return Response.json({ agent, signedIn: !!token });
    }
    formData()

    Read fields from a submitted HTML form instead of a JSON body.

    export async function POST(request: Request) {
      const form = await request.formData();
      const email = String(form.get('email'));
      return Response.json({ email });
    }

Sending Responses

    Response.json()

    Return JSON and set the status code with the second argument.

    return Response.json(
      { error: 'Not found' },
      { status: 404 }
    );
    Headers

    Set response headers through the options object of a Response.

    return new Response('ok', {
      headers: {
        'Content-Type': 'text/plain',
        'Cache-Control': 'no-store',
      },
    });
    Set a Cookie

    Write a cookie from a handler with the cookies() helper.

    import { cookies } from 'next/headers';
    
    export async function POST() {
      (await cookies()).set('theme', 'dark');
      return Response.json({ ok: true });
    }
    redirect()

    Send the caller to another URL from inside a handler.

    import { redirect } from 'next/navigation';
    
    export async function GET() {
      redirect('/login');
    }

Caching and Limits

    Cached Helper

    Cache the data instead of the handler by moving the query into a cached function.

    async function getPosts() {
      'use cache';
      return sql`SELECT * FROM posts`;
    }
    
    export async function GET() {
      return Response.json(await getPosts());
    }
    force-static

    Prerender a GET handler at build time in apps without Cache Components.

    export const dynamic = 'force-static';
    
    export async function GET() {
      return Response.json({ version: '1.0' });
    }
    maxDuration

    Raise the time limit for a slow handler on platforms that support it.

    export const maxDuration = 30; // seconds

Tips

  1. Use a Server Component or Server Action for your own pages, and keep route.ts handlers for webhooks and outside clients.
  2. Type dynamic handlers with the global RouteContext helper, so params always matches the folder names in the path.

Warnings

  1. A folder cannot hold both route.ts and page.tsx, so put API handlers under a separate path like app/api.
  2. A GET handler is not cached by default, so every request runs your code unless you opt in to caching.

In Practice

FAQ