Skip to main content

RateLimitService

A unified, composable rate limiting system that handles all rate limiting needs across the application. This service provides both per-minute sliding window limits and daily quota limits with database persistence.

Overview

The RateLimitService is a singleton service that implements:

  • Sliding Window Rate Limiting: For per-minute API limits using in-memory storage
  • Daily Quota Limits: For resource-intensive operations with database persistence
  • Attack Detection: Progressive penalties for detected abuse patterns
  • Development Bypass: Configurable bypass for development environments
  • Fail-Fast Architecture: Works properly on all devices or fails explicitly with clear errors

Key Features

🔒 Multiple Rate Limiting Strategies

  • Memory-based: Fast sliding window limits for standard API requests
  • Database-backed: Persistent daily quotas for expensive operations
  • Progressive Penalties: Escalating timeouts for repeated violations

🛡️ Attack Protection

  • Automatic detection of abuse patterns
  • Progressive backoff periods (1min → 5min → 15min → 1hr → 6hr → 24hr)
  • Separate attack-specific rate limits

⚙️ Reliable Configuration

  • Pre-configured limits for common scenarios
  • Custom rate limiting configurations
  • Per-endpoint customization
  • No fallbacks - works properly or fails with clear error messages

Usage

Basic Rate Limiting

import { RateLimitService } from '@lib/services/RateLimitService';

const rateLimiter = RateLimitService.getInstance();

// Check rate limit for API endpoint
const result = await rateLimiter.checkRateLimit({
identifier: req.ip,
configType: 'api'
});

if (!result.allowed) {
return new Response('Rate limit exceeded', {
status: 429,
headers: {
'Retry-After': result.retryAfter?.toString() || '60'
}
});
}

Daily Quota Limiting

// For expensive operations like OG image generation
const result = await rateLimiter.checkRateLimit({
identifier: userId,
configType: 'og-image' // Daily quota with database persistence
});

if (!result.allowed) {
return new Response('Daily quota exceeded', { status: 429 });
}

Custom Rate Limiting

// Create custom rate limit configuration
const result = await rateLimiter.checkRateLimit({
identifier: 'custom-operation',
configType: 'custom',
customConfig: {
windowMs: 5 * 60 * 1000, // 5 minutes
maxRequests: 10, // 10 requests per window
storage: 'memory'
}
});

Pre-configured Rate Limits

Config TypeWindowMax RequestsStorageUse Case
api1 minute100MemoryStandard API requests
auth1 minute500MemoryAuthentication endpoints
upload1 minute5MemoryFile uploads
search1 minute200MemorySearch/filter operations
admin1 minute50MemoryAdmin panel actions
sse1 minute1000MemoryServer-sent events
og-image24 hours1DatabaseDaily OG image quota
attack1 hour1MemoryDetected abuse patterns

API Reference

checkRateLimit(options: RateLimitOptions): Promise<RateLimitResult>

Main method to check if a request should be rate limited.

Parameters:

  • options.identifier: Unique identifier (IP address, user ID, etc.)
  • options.configType: Pre-configured rate limit type or 'custom'
  • options.customConfig: Custom configuration (required if configType is 'custom')
  • options.bypassDevelopment: Whether to bypass limits in development

Returns: RateLimitResult object with:

  • allowed: Whether the request is allowed
  • remaining: Number of requests remaining in window
  • resetTime: Timestamp when window resets
  • retryAfter: Seconds to wait before retrying (if blocked)
  • penaltyLevel: Current penalty level (0-5)
  • isAttack: Whether this is classified as an attack
  • quotaType: Type of quota applied

Throws: Error if unknown configuration type is provided

createAPILimiter(configType?: string)

Creates a middleware-style rate limiter for API routes.

createCustomLimiter(config: RateLimitConfig)

Creates a custom rate limiter with specific configuration.

resetRateLimit(identifier: string, configType?: string): void

Manually reset rate limit for a specific identifier.

getStats()

Get current rate limiting statistics and memory usage.

Attack Detection & Progressive Penalties

The service automatically detects abuse patterns and applies progressive penalties:

  1. Level 0: Normal operation
  2. Level 1: 1-minute backoff after violation
  3. Level 2: 5-minute backoff
  4. Level 3: 15-minute backoff
  5. Level 4: 1-hour backoff
  6. Level 5: 6-hour backoff
  7. Level 6+: 24-hour backoff

Penalty levels increase with repeated violations and decay over time.

Fail-Fast Architecture

The service uses a fail-fast approach for maximum reliability:

  • Direct Dependencies: All dependencies are imported directly
  • Explicit Errors: Unknown configurations throw clear error messages
  • No Silent Failures: Service works properly or fails with actionable errors
  • Consistent Behavior: Same behavior across all devices and environments

Environment Configuration

Development Bypass

Set BYPASS_RATE_LIMIT=true in your environment to disable rate limiting during development.

Database Configuration

For daily quotas, ensure your database service is properly configured. The service requires DatabaseService to be available for database-backed rate limiting.

Example: API Route Implementation

// app/api/example/route.ts
import { RateLimitService } from '@lib/services/RateLimitService';
import { getClientIP } from '@lib/utils/request';

export async function GET(request: Request) {
const rateLimiter = RateLimitService.getInstance();
const clientIP = getClientIP(request);

// Check rate limit
const rateLimit = await rateLimiter.checkRateLimit({
identifier: clientIP,
configType: 'api'
});

if (!rateLimit.allowed) {
return new Response(
JSON.stringify({
error: 'Rate limit exceeded',
retryAfter: rateLimit.retryAfter,
quotaType: rateLimit.quotaType
}),
{
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': rateLimit.retryAfter?.toString() || '60',
'X-RateLimit-Remaining': rateLimit.remaining.toString(),
'X-RateLimit-Reset': rateLimit.resetTime.toString()
}
}
);
}

// Process request normally
return new Response(JSON.stringify({ data: 'success' }));
}

Security Considerations

  • IP-based Limiting: Use client IP for anonymous rate limiting
  • User-based Limiting: Use authenticated user ID for user-specific limits
  • Attack Mitigation: Progressive penalties help mitigate sustained attacks
  • Resource Protection: Daily quotas protect expensive operations

Performance Notes

  • Memory storage provides sub-millisecond lookup times
  • Database storage adds ~2-5ms latency for daily quotas
  • Automatic cleanup prevents memory leaks
  • Singleton pattern ensures efficient resource usage

Error Handling

The service throws explicit errors for:

  • Unknown configuration types
  • Missing required dependencies
  • Database connection failures (for database-backed limits)

This ensures issues are caught early and resolved rather than silently failing.


File: src/lib/services/RateLimitService.ts
Last Updated: 2025-01-15