Next.js offers powerful built-in API Routes that let you build full-stack applications without a separate backend. Here's what most developers miss about the Next.js API capabilities.
What Makes Next.js API Routes Special
Next.js API Routes run as serverless functions. You create a file in pages/api/ (Pages Router) or app/api/ (App Router), and it becomes an API endpoint automatically.
// app/api/hello/route.js (App Router)
export async function GET(request) {
return Response.json({ message: 'Hello from Next.js API!' })
}
export async function POST(request) {
const body = await request.json()
return Response.json({ received: body })
}
Route Handlers in App Router
The App Router introduced Route Handlers — a cleaner way to define API endpoints:
// app/api/users/[id]/route.js
export async function GET(request, { params }) {
const { id } = await params
const user = await fetchUser(id)
return Response.json(user)
}
export async function PUT(request, { params }) {
const { id } = await params
const body = await request.json()
const updated = await updateUser(id, body)
return Response.json(updated)
}
export async function DELETE(request, { params }) {
const { id } = await params
await deleteUser(id)
return new Response(null, { status: 204 })
}
Middleware — Intercept Every Request
Next.js Middleware runs before every request, giving you powerful control:
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
// Rate limiting
const ip = request.headers.get('x-forwarded-for')
// Auth check
const token = request.cookies.get('session')
if (!token && request.nextUrl.pathname.startsWith('/api/protected')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Add custom headers
const response = NextResponse.next()
response.headers.set('x-request-id', crypto.randomUUID())
return response
}
export const config = {
matcher: '/api/:path*'
}
Server Actions — RPC Without an API
Server Actions let you call server functions directly from client components:
// app/actions.js
'use server'
export async function createPost(formData) {
const title = formData.get('title')
const content = formData.get('content')
const post = await db.post.create({
data: { title, content }
})
return post
}
// app/components/PostForm.jsx
'use client'
import { createPost } from '../actions'
export function PostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="Post title" />
<textarea name="content" placeholder="Write something..." />
<button type="submit">Create Post</button>
</form>
)
}
Streaming & Edge Runtime
Next.js API Routes can stream responses and run on the Edge:
// app/api/stream/route.js
export const runtime = 'edge'
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(
encoder.encode(`data: Message ${i}\n\n`)
)
await new Promise(r => setTimeout(r, 500))
}
controller.close()
}
})
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
}
})
}
Caching & Revalidation
Control caching at the route level:
// app/api/products/route.js
export const revalidate = 3600 // Revalidate every hour
export async function GET() {
const products = await fetch('https://api.store.com/products', {
next: { revalidate: 3600 }
})
return Response.json(await products.json())
}
Key Takeaways
- API Routes turn files into serverless endpoints automatically
- Route Handlers (App Router) support all HTTP methods
- Middleware intercepts requests for auth, rate limiting, redirects
- Server Actions eliminate the API layer for form submissions
- Edge Runtime for low-latency global endpoints
- Streaming for real-time data delivery
Check out Next.js docs for the full API reference.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)