Learn how to create and manage API routes in your Next.js application using Route Handlers.
Create API endpoints using Route Handlers in the app directory:
1import { NextResponse } from 'next/server'2import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'3import { cookies } from 'next/headers'45export async function GET(request: Request) {6 const supabase = createRouteHandlerClient({ cookies })7 const { searchParams } = new URL(request.url)8 const query = searchParams.get('query')910 const { data, error } = await supabase11 .from('posts')12 .select()13 .ilike('title', `%${query}%`)14 .limit(10)1516 if (error) {17 return NextResponse.json(18 { error: 'Failed to fetch posts' },19 { status: 500 }20 )21 }2223 return NextResponse.json(data)24}2526export async function POST(request: Request) {27 const supabase = createRouteHandlerClient({ cookies })28 const json = await request.json()2930 const { data, error } = await supabase31 .from('posts')32 .insert(json)33 .select()34 .single()3536 if (error) {37 return NextResponse.json(38 { error: 'Failed to create post' },39 { status: 500 }40 )41 }4243 return NextResponse.json(data, { status: 201 })44}
Create middleware to handle authentication, logging, and other common functionality:
1import { NextResponse } from 'next/server'2import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'3import { type NextRequest } from 'next/server'45export async function middleware(request: NextRequest) {6 const res = NextResponse.next()7 const supabase = createMiddlewareClient({ req: request, res })89 // Verify authentication10 const {11 data: { session },12 } = await supabase.auth.getSession()1314 // Protected API routes pattern15 if (request.nextUrl.pathname.startsWith('/api/protected')) {16 if (!session) {17 return NextResponse.json(18 { error: 'Unauthorized' },19 { status: 401 }20 )21 }2223 // Add user ID to request headers24 const requestHeaders = new Headers(request.headers)25 requestHeaders.set('x-user-id', session.user.id)2627 return NextResponse.next({28 request: {29 headers: requestHeaders,30 },31 })32 }3334 return res35}3637export const config = {38 matcher: '/api/:path*',39}
Validate API requests using Zod schemas:
1import { NextResponse } from 'next/server'2import { z } from 'zod'34const postSchema = z.object({5 title: z.string().min(1).max(100),6 content: z.string().min(1),7 published: z.boolean().default(false),8 authorId: z.string().uuid(),9})1011export async function POST(request: Request) {12 try {13 const json = await request.json()14 const body = postSchema.parse(json)1516 // Your logic here17 const post = await createPost(body)1819 return NextResponse.json(post, { status: 201 })20 } catch (error) {21 if (error instanceof z.ZodError) {22 return NextResponse.json(23 { error: 'Invalid request data', details: error.errors },24 { status: 400 }25 )26 }2728 return NextResponse.json(29 { error: 'Internal server error' },30 { status: 500 }31 )32 }33}
Implement consistent error handling across your API routes:
1// Custom error classes2export class APIError extends Error {3 constructor(4 message: string,5 public status: number,6 public code?: string7 ) {8 super(message)9 this.name = 'APIError'10 }11}1213export class ValidationError extends APIError {14 constructor(message: string) {15 super(message, 400, 'VALIDATION_ERROR')16 }17}1819export class AuthorizationError extends APIError {20 constructor(message: string = 'Unauthorized') {21 super(message, 401, 'UNAUTHORIZED')22 }23}2425// Error handler utility26export function handleError(error: unknown) {27 if (error instanceof APIError) {28 return NextResponse.json(29 {30 error: error.message,31 code: error.code,32 },33 { status: error.status }34 )35 }3637 console.error('Unhandled error:', error)38 return NextResponse.json(39 {40 error: 'Internal server error',41 code: 'INTERNAL_SERVER_ERROR',42 },43 { status: 500 }44 )45}4647// Usage in route handler48export async function GET() {49 try {50 const user = await getCurrentUser()51 if (!user) {52 throw new AuthorizationError()53 }5455 if (!user.isAdmin) {56 throw new APIError('Forbidden', 403, 'FORBIDDEN')57 }5859 const data = await fetchData()60 return NextResponse.json(data)61 } catch (error) {62 return handleError(error)63 }64}
Configure CORS (Cross-Origin Resource Sharing) for your API routes:
1import { type NextRequest } from 'next/server'23const allowedOrigins = [4 'https://your-app.com',5 'https://staging.your-app.com',6]78const corsHeaders = {9 'Access-Control-Allow-Origin': '*',10 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',11 'Access-Control-Allow-Headers': 'Content-Type, Authorization',12}1314export async function OPTIONS(request: NextRequest) {15 const origin = request.headers.get('origin') ?? ''1617 if (allowedOrigins.includes(origin)) {18 return new Response(null, {19 status: 204,20 headers: {21 ...corsHeaders,22 'Access-Control-Allow-Origin': origin,23 },24 })25 }2627 return new Response(null, { status: 204 })28}2930export async function GET(request: NextRequest) {31 const origin = request.headers.get('origin') ?? ''3233 // Your route logic here34 const data = { message: 'Hello, World!' }3536 return new Response(JSON.stringify(data), {37 headers: {38 'Content-Type': 'application/json',39 ...(allowedOrigins.includes(origin) && {40 'Access-Control-Allow-Origin': origin,41 }),42 },43 })44}
Implement rate limiting to protect your API from abuse:
1import { Redis } from '@upstash/redis'2import { Ratelimit } from '@upstash/ratelimit'3import { headers } from 'next/headers'45const redis = Redis.fromEnv()67const ratelimit = new Ratelimit({8 redis,9 limiter: Ratelimit.slidingWindow(10, '10 s'),10})1112export async function withRateLimit(handler: Function) {13 const ip = headers().get('x-forwarded-for') ?? '127.0.0.1'14 const { success, limit, reset, remaining } = await ratelimit.limit(ip)1516 if (!success) {17 return Response.json(18 {19 error: 'Too many requests',20 limit,21 reset,22 remaining,23 },24 {25 status: 429,26 headers: {27 'X-RateLimit-Limit': limit.toString(),28 'X-RateLimit-Remaining': remaining.toString(),29 'X-RateLimit-Reset': reset.toString(),30 },31 }32 )33 }3435 return handler()36}3738// Usage in route handler39export async function GET(request: Request) {40 return withRateLimit(async () => {41 const data = await fetchData()42 return Response.json(data)43 })44}