Crash Courses · Masters
Rate Limiting & Error Handling
Protect REST APIs from abuse with rate limiting, CORS, and consistent error responses.
10 min read+175 XP on completionCert: REST APIs Crash Course
Tap any word in the text below to start reading from there.
Rate Limiting & Error Handling
Public APIs need protection. Rate limiting, CORS, consistent error responses, and security headers are the baseline for production APIs.
Why Error Handling Strategy Matters
Inconsistent errors cause bugs. If one endpoint returns { message: "Not found" } and another returns { error: "Course not found" } and a third returns a plain string — every client has to handle each case differently. A consistent error envelope means one parsing function handles all errors.
Rate Limiting (Vercel + upstash/ratelimit)
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10s'), // 10 req per 10s
})
export async function POST(req: NextRequest) {
const ip = req.headers.get('x-forwarded-for') ?? '127.0.0.1'
const { success, reset } = await ratelimit.limit(ip)
if (!success) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: { 'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)) },
}
)
}
// process request...
}Consistent Error Envelope
// lib/api-helpers.ts — response factory functions
export function ok<T>(data: T, status = 200) {
return NextResponse.json({ data }, { status })
}
export function created<T>(data: T) {
return NextResponse.json({ data }, { status: 201 })
}
export function notFound(message = 'Not found') {
return NextResponse.json(
{ error: { code: 'NOT_FOUND', message } },
{ status: 404 }
)
}
export function badRequest(message: string) {
return NextResponse.json(
{ error: { code: 'BAD_REQUEST', message } },
{ status: 400 }
)
}
export function serverError(message = 'Internal server error') {
return NextResponse.json(
{ error: { code: 'SERVER_ERROR', message } },
{ status: 500 }
)
}
// Usage — same shape everywhere
export async function GET(req, { params }) {
const { id } = await params
const course = await db.findById(id)
if (!course) return notFound('Course not found')
return ok(course)
}CORS Headers
// next.config.ts — global CORS headers
async headers() {
return [{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://yourdomain.com' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,PATCH,DELETE,OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
],
}]
}
// Route Handler for OPTIONS preflight
export async function OPTIONS() {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
},
})
}Reading progress
0% read
In this module
Rate limiting
CORS
Error envelope
Input sanitization
0%