This guide will cover how you can build APIs with Next.js, including setting up your project, understanding the App Router and Route Handlers, handling multiple HTTP methods, implementing dynamic routing, creating reusable middleware logic, and deciding when to spin up a dedicated API layer.
Pages Router: Historically, Next.js used pages/api/* for APIs. This approach relied on Node.js request/response objects and an Express-like API.
App Router (Default): Introduced in Next.js 13, the App Router fully embraces web standard Request/Response APIs. Instead of pages/api/*, you can now place route.ts or route.js files anywhere inside the app/ directory.
Why switch? The App Router’s “Route Handlers” lean on the Web Platform Request/Response APIs rather than Node.js-specific APIs. This simplifies learning, reduces friction, and helps you reuse your knowledge across different tools.
You can build a public API that’s consumed by your Next.js web app, a separate mobile app, or any third-party service. For example, you might fetch from /api/users both in your React website and a React Native mobile app.
Proxy to an Existing Backend
Sometimes you want to hide or consolidate external microservices behind a single endpoint. Next.js Route Handlers can act as a proxy or middle layer to another existing backend. For instance, you might intercept requests, handle authentication, transform data, and then pass the request along to an upstream API.
Webhooks and Integrations
If you receive external callbacks or webhooks (e.g., from Stripe, GitHub, Twilio), you can handle them with Route Handlers.
Custom Authentication
If you need sessions, tokens, or other auth logic, you can store cookies, read headers, and respond with the appropriate data in your Next.js API layer.
Note: If you only need server-side data fetching for your own Next.js app (and you don’t need to share that data externally), Server Components might be sufficient to fetch data directly during render—no separate API layer is required.
Unlike the Pages Router API routes (which had a single default export), you can export multiple functions representing different HTTP methods from the same file.
app/api/users/route.ts
export async function GET(request: Request) { // For example, fetch data from your DB here const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]; return new Response(JSON.stringify(users), { status: 200, headers: { 'Content-Type': 'application/json' } });}export async function POST(request: Request) { // Parse the request body const body = await request.json(); const { name } = body; // e.g. Insert new user into your DB const newUser = { id: Date.now(), name }; return new Response(JSON.stringify(newUser), { status: 201, headers: { 'Content-Type': 'application/json' } });}
Now, sending a GET request to /api/users returns your list of users, while a POST request to the same URL will insert a new one.
import { NextRequest } from 'next/server';import { cookies, headers } from 'next/headers';export async function GET(request: NextRequest) { // 1. Using 'next/headers' helpers const cookieStore = await cookies(); const token = cookieStore.get('token'); const headersList = await headers(); const referer = headersList.get('referer'); // 2. Using the standard Web APIs const userAgent = request.headers.get('user-agent'); return new Response(JSON.stringify({ token, referer, userAgent }), { headers: { 'Content-Type': 'application/json' }, });}
The cookies() and headers() functions can be helpful if you plan to re-use shared logic across other server-side code in Next.js. You'll notice Next.js also provides NextRequest and NextResponse which extend the base Web APIs.
To create dynamic paths (e.g. /api/users/:id), use Dynamic Segments in your folder structure:
app└── api └── users └── [id] └── route.ts
This file corresponds to a URL like /api/users/123, with the 123 captured as a parameter.
app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> },) { const id = (await params).id; // e.g. Query a database for user with ID `id` return new Response(JSON.stringify({ id, name: `User ${id}` }), { status: 200, headers: { 'Content-Type': 'application/json' }, });}export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> },) { const id = (await params).id; // e.g. Delete user with ID `id` in DB return new Response(null, { status: 204 });}
A common scenario is proxying an existing backend service. You can authenticate requests, handle logging, or transform data before sending it to a remote server or backend:
app/api/external/route.ts
import { NextRequest } from 'next/server';export async function GET(request: NextRequest) { const response = await fetch('https://example.com/api/data', { // Optional: forward some headers, add auth tokens, etc. headers: { Authorization: `Bearer ${process.env.API_TOKEN}` }, }); // Transform or forward the response const data = await response.json(); const transformed = { ...data, source: 'proxied-through-nextjs' }; return new Response(JSON.stringify(transformed), { headers: { 'Content-Type': 'application/json' }, });}
Now your clients only need to call /api/external, and Next.js will handle the rest. This is also sometimes called a “Backend for Frontend” or BFF.
If you want to apply the same logic (e.g. authentication checks, logging) across multiple Route Handlers, you can create reusable functions that wrap your handlers:
lib/with-auth.ts
import { NextRequest } from 'next/server';type Handler = (req: NextRequest, context?: any) => Promise<Response>;export function withAuth(handler: Handler): Handler { return async (req, context) => { const token = req.cookies.get('token')?.value; if (!token) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }); } // If authenticated, call the original handler return handler(req, context); };}
Then in your Route Handler:
app/api/secret/route.ts
import { NextRequest } from 'next/server';import { withAuth } from '@/lib/with-auth';async function secretGET(request: NextRequest) { return new Response(JSON.stringify({ secret: 'Here be dragons' }), { headers: { 'Content-Type': 'application/json' }, });}export const GET = withAuth(secretGET);
The standard Next.js server deployment using next start enables you to use features like Route Handlers, Server Components, Middleware and more – while taking advantage of dynamic, request time information.
There is no additional configuration required. See Deploying for more details.
import type { NextConfig } from 'next';const nextConfig: NextConfig = { output: 'export',};export default nextConfig;
In static export mode, Next.js will generate purely static HTML, CSS, and JS. You cannot run server-side code (like API endpoints). If you still need an API, you’d have to host it separately (e.g., a standalone Node.js server).
Note:
GET Route Handlerscan be statically exported if they don’t rely on dynamic request data. They become static files in your out folder.
All other server features (dynamic requests, rewriting cookies, etc.) are not supported in a pure SPA export.
If you are deploying your Next.js application to Vercel, we have a guide on deploying APIs. This includes other Vercel features like programmatic rate-limiting through the Vercel Firewall. Vercel also offers Cron Jobs, which are commonly needed with API approaches.
You can think of Server Actions like automatically generated POST API routes that can be called from the client.
They are designed for mutation operations, such as creating, updating, or deleting data. You call a Server Action like a normal JavaScript function, versus making an explicit fetch to a defined API route.
While there is still a network request happening, you don't need to manage it explicitly. The URL path is auto-generated and encrypted, so you can't manually access a route like /api/users in the browser.
If you plan to use Server Actions and expose a public API, we recommend moving the core logic to a Data Access Layer and calling the same logic from both the Server Action and the API route.
This guide will cover how you can build APIs with Next.js, including setting up your project, understanding the App Router and Route Handlers, handling multiple HTTP methods, implementing dynamic routing, creating reusable middleware logic, and deciding when to spin up a dedicated API layer.
Copy link to heading1. Getting started
Copy link to heading1.1 Create a Next.js app
If you’re starting fresh, you can create a new Next.js project using:
Copy link to heading1.2 App Router vs. Pages Router
pages/api/*for APIs. This approach relied on Node.js request/response objects and an Express-like API.pages/api/*, you can now placeroute.tsorroute.jsfiles anywhere inside theapp/directory.Copy link to heading2. Why (and when) to build APIs with Next.js
Public API for Multiple Clients
Proxy to an Existing Backend
Webhooks and Integrations
Custom Authentication
Copy link to heading3. Creating an API with Route Handlers
Copy link to heading3.1 Basic file setup
In the App Router (
app/), create a folder that represents your route, and inside it, aroute.tsfile.For example, to create an endpoint at
/api/users:Copy link to heading3.2 Multiple HTTP methods in one file
Unlike the Pages Router API routes (which had a single default export), you can export multiple functions representing different HTTP methods from the same file.
Now, sending a GET request to
/api/usersreturns your list of users, while aPOSTrequest to the same URL will insert a new one.Copy link to heading4. Working with Web APIs
Copy link to heading4.1 Directly using Request & Response
By default, your Route Handler methods (
GET,POST, etc.) receive a standard Request object, and you must return a standard Response object.Copy link to heading4.2 Query parameters
Copy link to heading4.3 Headers and cookies
The
cookies()andheaders()functions can be helpful if you plan to re-use shared logic across other server-side code in Next.js. You'll notice Next.js also providesNextRequestandNextResponsewhich extend the base Web APIs.Copy link to heading5. Dynamic routes
To create dynamic paths (e.g.
/api/users/:id), use Dynamic Segments in your folder structure:This file corresponds to a URL like
/api/users/123, with the123captured as a parameter.Here,
params.idgives you the dynamic segment.Copy link to heading6. Using Next.js as a proxy or forwarding layer
A common scenario is proxying an existing backend service. You can authenticate requests, handle logging, or transform data before sending it to a remote server or backend:
Now your clients only need to call
/api/external, and Next.js will handle the rest. This is also sometimes called a “Backend for Frontend” or BFF.Copy link to heading7. Building shared “middleware” logic
If you want to apply the same logic (e.g. authentication checks, logging) across multiple Route Handlers, you can create reusable functions that wrap your handlers:
Then in your Route Handler:
Copy link to heading8. Deployment and “SPA Mode” considerations
Copy link to heading8.1 Standard Node.js deployment
The standard Next.js server deployment using next start enables you to use features like Route Handlers, Server Components, Middleware and more – while taking advantage of dynamic, request time information.
There is no additional configuration required. See Deploying for more details.
Copy link to heading8.2 SPA/Static Export
Next.js also supports outputting your entire site as a static Single-Page Application (SPA).
You can enable this by setting:
In static export mode, Next.js will generate purely static HTML, CSS, and JS. You cannot run server-side code (like API endpoints). If you still need an API, you’d have to host it separately (e.g., a standalone Node.js server).
Copy link to heading8.3 Deploying APIs on Vercel
If you are deploying your Next.js application to Vercel, we have a guide on deploying APIs. This includes other Vercel features like programmatic rate-limiting through the Vercel Firewall. Vercel also offers Cron Jobs, which are commonly needed with API approaches.
Copy link to heading9. When to skip creating an API endpoint
With the App Router’s React Server Components, you can fetch data directly on the server without exposing a public endpoint:
If your data is only used inside your Next.js app, you may not need a public API at all.
Copy link to heading10. Putting It All Together
npx create-next-app@latest --api.app/directory (e.g.,app/api/users/route.ts).GET,POST,PUT,DELETE, etc.) in the same file.Requestobject and return aResponse.fetch('/api/...')).withAuth()) for auth or other repeated logic.Copy link to headingConclusion
Using the Next.js App Router and Route Handlers gives you a flexible, modern way to build APIs that embrace the Web Platform directly. You can:
[id]segment folder structure.Copy link to headingFrequently Asked Questions
Copy link to headingWhat about Server Actions?
You can think of Server Actions like automatically generated
POSTAPI routes that can be called from the client.They are designed for mutation operations, such as creating, updating, or deleting data. You call a Server Action like a normal JavaScript function, versus making an explicit
fetchto a defined API route.While there is still a network request happening, you don't need to manage it explicitly. The URL path is auto-generated and encrypted, so you can't manually access a route like
/api/usersin the browser.If you plan to use Server Actions and expose a public API, we recommend moving the core logic to a Data Access Layer and calling the same logic from both the Server Action and the API route.
Copy link to headingCan I use TypeScript with Route Handlers?
Yes, you can use TypeScript with Route Handlers. For example, defining the
RequestandResponsetypes in yourroutefile.Learn more about TypeScript with Next.js.
Copy link to headingWhat are the best practices for authentication?
Learn more in our authentication documentation.