typescriptintermediate

API Route Rate Limiting

Add rate limiting to Next.js API routes with sliding window, IP-based limits, and custom responses.

typescript
// app/api/lib/rate-limit.ts
import { NextRequest, NextResponse } from 'next/server';

interface RateLimitConfig {
  maxRequests: number;
  windowMs: number;
  keyFn?: (req: NextRequest) => string;
  message?: string;
}

interface Entry {
  tokens: number;
  lastRefill: number;
}

const store = new Map<string, Entry>();

// Cleanup old entries periodically
setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of store) {
    if (now - entry.lastRefill > 300_000) store.delete(key);
  }
}, 60_000);

function rateLimit(config: RateLimitConfig) {
  const {
    maxRequests,
    windowMs,
    keyFn = (req) => req.headers.get('x-forwarded-for') ?? 'anonymous',
    message = 'Too many requests, please try again later.',
  } = config;

  return (req: NextRequest): NextResponse | null => {
    const key = keyFn(req);
    const now = Date.now();
    let entry = store.get(key);

    if (!entry) {
      entry = { tokens: maxRequests - 1, lastRefill: now };
      store.set(key, entry);
      return null; // Allow
    }

    // Token bucket refill
    const elapsed = now - entry.lastRefill;
    const refill = Math.floor((elapsed / windowMs) * maxRequests);
    entry.tokens = Math.min(maxRequests, entry.tokens + refill);
    entry.lastRefill = now;

    if (entry.tokens <= 0) {
      return NextResponse.json(
        { error: message },
        {
          status: 429,
          headers: {
            'Retry-After': String(Math.ceil(windowMs / 1000)),
            'X-RateLimit-Limit': String(maxRequests),
            'X-RateLimit-Remaining': '0',
          },
        },
      );
    }

    entry.tokens--;
    return null; // Allow
  };
}

// app/api/data/route.ts
const limiter = rateLimit({ maxRequests: 30, windowMs: 60_000 });

export async function GET(req: NextRequest) {
  const limited = limiter(req);
  if (limited) return limited;

  return NextResponse.json({ data: 'Hello from rate-limited API!' });
}

export async function POST(req: NextRequest) {
  const limited = limiter(req);
  if (limited) return limited;

  const body = await req.json();
  return NextResponse.json({ received: body });
}

export { rateLimit };

Use Cases

  • API abuse prevention
  • Fair usage enforcement
  • DDoS mitigation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.