Skip to Content
Welcome to the official DBS Documentation! 📚
AdvancedRate Limiting

Rate Limiting

DBS includes a lightweight in-memory rate limiter that protects all API routes from excessive requests. It is built into apiGuard and applied automatically to every protected route.

Default Behavior

Every call to apiGuard() automatically enforces rate limiting:

  • Limit: 200 requests per 60 seconds
  • Key: Per user ID (authenticated users are rate-limited individually)
  • Storage: In-memory (Map in the Node.js process)
  • Response on exceed: 429 Too Many Requests
// This already includes rate limiting: const guard = await apiGuard('user.read'); if (guard.error) return guard.error; // could be 429

Implementation

The rate limiter lives in src/lib/rate-limit.ts:

// src/lib/rate-limit.ts interface RateLimitConfig { maxRequests: number; // e.g., 200 windowMs: number; // e.g., 60_000 (60 seconds) } interface RateLimitEntry { count: number; resetAt: number; } const store = new Map<string, RateLimitEntry>(); export function checkRateLimit( key: string, config: RateLimitConfig = { maxRequests: 200, windowMs: 60_000 } ): { limited: boolean; remaining: number; resetAt: number } { const now = Date.now(); const entry = store.get(key); if (!entry || now > entry.resetAt) { store.set(key, { count: 1, resetAt: now + config.windowMs }); return { limited: false, remaining: config.maxRequests - 1, resetAt: now + config.windowMs }; } entry.count += 1; if (entry.count > config.maxRequests) { return { limited: true, remaining: 0, resetAt: entry.resetAt }; } return { limited: false, remaining: config.maxRequests - entry.count, resetAt: entry.resetAt }; }

Custom Rate Limits Per Route

If a specific route needs tighter or looser limits, apply the rate limiter directly before or after apiGuard:

// src/app/api/auth/send-otp/route.ts import { checkRateLimit } from '@/lib/rate-limit'; import { apiGuard } from '@/lib/api-guard'; export async function POST(req: Request) { const guard = await apiGuard(); if (guard.error) return guard.error; // Maximum 5 OTP emails per 10 minutes const { limited } = checkRateLimit(`otp:${guard.session.user.id}`, { maxRequests: 5, windowMs: 10 * 60 * 1000, }); if (limited) { return Response.json( { error: 'Too many OTP requests. Please wait before trying again.' }, { status: 429 } ); } // send OTP... }

Error Response

When rate limiting triggers, the API returns:

HTTP/1.1 429 Too Many Requests Content-Type: application/json { "error": "Rate limit exceeded, please try again later." }

The frontend’s error handling in useData and the service layer surfaces this as a toast notification.

The in-memory rate limiter is per-instance. In a horizontally scaled environment (multiple Node.js processes or containers), each instance maintains its own count. For strict rate limiting across multiple instances, consider using a Redis-backed rate limiter (e.g., @upstash/ratelimit). Only the implementation in rate-limit.ts needs to change — apiGuard remains the same.

Rate Limit Headers

Consider adding standard headers to rate-limited responses for better client integration:

return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', 'X-RateLimit-Limit': String(config.maxRequests), 'X-RateLimit-Remaining': String(remaining), 'X-RateLimit-Reset': String(Math.floor(resetAt / 1000)), 'Retry-After': String(Math.ceil((resetAt - Date.now()) / 1000)), }, });
Last updated on