Overview
Use Upstash Redis for rate limiting with preset configurations. Protects against abuse and ensures fair resource usage.
What it is
Sliding window rate limiting with configurable presets (strict, standard, relaxed, ai) and per-user/per-IP tracking.
Why we use it
Prevent brute force attacks, protect expensive operations, and ensure service availability under load.
When to use
All API endpoints, especially authentication, AI generation, and any resource-intensive operations.
Key Features
- Preset configurations (strict, standard, relaxed, ai)
- Per-user or per-IP rate limiting
- Sliding window algorithm
- Rate limit headers for transparency
Quick Start
Apply Rate Limiting
Add rate limiting to an API route.
// Apply rate limiting to an API route
import { rateLimit } from '@/lib/middleware/security';
import { handleError } from '@/lib/errors';
export async function POST(request: Request) {
try {
// Check rate limit before processing
const rateLimitResult = await rateLimit(request, 'standard');
if (!rateLimitResult.success) {
return NextResponse.json(
{ ok: false, error: { code: 'RATE_LIMITED', message: 'Too many requests' } },
{
status: 429,
headers: {
'Retry-After': String(rateLimitResult.reset),
'X-RateLimit-Limit': String(rateLimitResult.limit),
'X-RateLimit-Remaining': String(rateLimitResult.remaining),
}
}
);
}
// Process request...
return NextResponse.json({ ok: true, data: result });
} catch (error) {
return handleError(error);
}
}Patterns
Rate Limit Presets
Choose appropriate limits by endpoint type.
// Rate limit presets
import { rateLimit, RATE_LIMIT_PRESETS } from '@/lib/middleware/security';
// Available presets:
// 'strict' - 10 requests per minute (login, password reset)
// 'standard' - 60 requests per minute (general API)
// 'relaxed' - 300 requests per minute (read-heavy endpoints)
// 'ai' - 20 requests per minute (AI generation)
// Login endpoint - strict limiting
export async function POST(request: Request) {
const result = await rateLimit(request, 'strict');
// ...
}
// List endpoint - relaxed limiting
export async function GET(request: Request) {
const result = await rateLimit(request, 'relaxed');
// ...
}Per-User Limiting
Rate limit by user ID when authenticated.
// Per-user rate limiting
import { rateLimit } from '@/lib/middleware/security';
import { auth } from '@/lib/middleware/auth';
export async function POST(request: Request) {
const session = await auth();
// Rate limit by user ID if authenticated, IP otherwise
const identifier = session?.user.id ?? getClientIP(request);
const result = await rateLimit(request, 'standard', {
identifier,
});
if (!result.success) {
return NextResponse.json(
{ ok: false, error: { code: 'RATE_LIMITED', message: 'Too many requests' } },
{ status: 429 }
);
}
// Process request...
}Rate Limit Headers
Include headers for client transparency.
// Rate limit response headers
// Always include these headers for client transparency
const headers = {
'X-RateLimit-Limit': String(limit), // Max requests allowed
'X-RateLimit-Remaining': String(remaining), // Requests remaining
'X-RateLimit-Reset': String(reset), // Unix timestamp when limit resets
'Retry-After': String(retryAfter), // Seconds until retry allowed
};
return NextResponse.json(data, { headers });Watch Out
No rate limiting on sensitive endpoints
Don't
// No rate limiting on sensitive endpoint
export async function POST(request: Request) {
const { email, password } = await request.json();
// Allows unlimited login attempts - vulnerable to brute force!
const user = await attemptLogin(email, password);
return NextResponse.json({ ok: true, data: user });
}Do
// Rate limited sensitive endpoint
export async function POST(request: Request) {
const rateLimitResult = await rateLimit(request, 'strict');
if (!rateLimitResult.success) {
return NextResponse.json(
{ ok: false, error: { code: 'RATE_LIMITED', message: 'Too many attempts' } },
{ status: 429, headers: { 'Retry-After': '60' } }
);
}
const { email, password } = await request.json();
const user = await attemptLogin(email, password);
return NextResponse.json({ ok: true, data: user });
}Overly strict limits affecting UX
Don't
// Overly strict limits on read endpoints
export async function GET(request: Request) {
// 10 requests/minute for a read endpoint is too strict
const result = await rateLimit(request, 'strict');
// Users will constantly hit rate limits during normal use
// ...
}Do
// Appropriate limits based on endpoint type
export async function GET(request: Request) {
// 300 requests/minute for read endpoints
const result = await rateLimit(request, 'relaxed');
// ...
}
export async function POST(request: Request) {
// 60 requests/minute for standard mutations
const result = await rateLimit(request, 'standard');
// ...
}- Missing bypass for internal services
- Not including rate limit headers