Limitly
Examples

Custom Rate Limit Strategies

Implement custom rate limiting strategies for different use cases. Learn patterns for per-endpoint limits, adaptive limits, and tier-based systems.

Custom Rate Limit Strategies

Learn how to implement custom rate limiting strategies tailored to your specific use cases. These patterns can be combined and adapted to fit your application's needs.

Strategy 1: Per-Endpoint Limits

Different endpoints have different resource requirements. Apply appropriate limits:

import { createClient } from 'limitly-sdk';

const client = createClient({ serviceId: 'endpoint-based' });

// Define limits per endpoint based on resource usage
const endpointLimits: Record<string, { capacity: number; refillRate: number }> = {
  // Authentication endpoints - very strict
  '/api/login': { capacity: 5, refillRate: 0.1 },        // 5 attempts, 1 per 10 seconds
  '/api/register': { capacity: 3, refillRate: 0.05 },    // 3 attempts, 1 per 20 seconds
  '/api/reset-password': { capacity: 3, refillRate: 0.05 },
  
  // Data endpoints - moderate limits
  '/api/data': { capacity: 100, refillRate: 10 },        // 100 requests, 10 per second
  '/api/search': { capacity: 50, refillRate: 5 },        // 50 requests, 5 per second
  
  // Heavy operations - strict limits
  '/api/export': { capacity: 10, refillRate: 0.5 },      // 10 exports, 1 per 2 seconds
  '/api/generate-report': { capacity: 5, refillRate: 0.2 }, // 5 reports, 1 per 5 seconds
  '/api/bulk-upload': { capacity: 3, refillRate: 0.1 },  // 3 uploads, 1 per 10 seconds
  
  // Light operations - lenient limits
  '/api/health': { capacity: 1000, refillRate: 100 },    // 1000 requests, 100 per second
  '/api/ping': { capacity: 1000, refillRate: 100 }
};

async function protectEndpoint(endpoint: string, userId: string) {
  // Get limits for this endpoint, or use defaults
  const limits = endpointLimits[endpoint] || { 
    capacity: 50, 
    refillRate: 5 
  };
  
  // Use endpoint in identifier to separate limits per endpoint
  const result = await client.checkRateLimit({
    identifier: `${userId}:${endpoint}`,
    ...limits
  });
  
  return result;
}

// Usage in your API
async function handleRequest(endpoint: string, userId: string) {
  const result = await protectEndpoint(endpoint, userId);
  
  if (!result.allowed) {
    return {
      error: 'Rate limit exceeded for this endpoint',
      endpoint,
      retryAfter: result.reset 
        ? Math.ceil((result.reset - Date.now()) / 1000) 
        : 60
    };
  }
  
  // Process request
  return { success: true };
}

Strategy 2: Adaptive Limits Based on Load

Dynamically adjust rate limits based on system load:

import { createClient } from 'limitly-sdk';

const client = createClient({ serviceId: 'adaptive' });

// Simulate system monitoring
interface SystemMetrics {
  cpuUsage: number;      // 0-100
  memoryUsage: number;   // 0-100
  requestQueue: number;  // Number of queued requests
}

async function getSystemMetrics(): Promise<SystemMetrics> {
  // In production, get from your monitoring system
  // (Prometheus, CloudWatch, Datadog, etc.)
  return {
    cpuUsage: 45,
    memoryUsage: 60,
    requestQueue: 10
  };
}

function calculateAdaptiveLimits(
  baseCapacity: number,
  baseRefillRate: number,
  metrics: SystemMetrics
): { capacity: number; refillRate: number } {
  // Calculate overall system load (weighted average)
  const systemLoad = (
    metrics.cpuUsage * 0.4 +
    metrics.memoryUsage * 0.4 +
    Math.min(metrics.requestQueue / 100, 1) * 100 * 0.2
  );
  
  // Reduce limits when system is under stress
  if (systemLoad > 80) {
    // Critical load - reduce to 30% of base
    return {
      capacity: Math.floor(baseCapacity * 0.3),
      refillRate: Math.floor(baseRefillRate * 0.3)
    };
  } else if (systemLoad > 60) {
    // High load - reduce to 50% of base
    return {
      capacity: Math.floor(baseCapacity * 0.5),
      refillRate: Math.floor(baseRefillRate * 0.5)
    };
  } else if (systemLoad > 40) {
    // Medium load - reduce to 75% of base
    return {
      capacity: Math.floor(baseCapacity * 0.75),
      refillRate: Math.floor(baseRefillRate * 0.75)
    };
  }
  
  // Normal load - use base limits
  return {
    capacity: baseCapacity,
    refillRate: baseRefillRate
  };
}

async function checkAdaptiveLimit(userId: string) {
  const metrics = await getSystemMetrics();
  const baseCapacity = 100;
  const baseRefillRate = 10;
  
  const limits = calculateAdaptiveLimits(baseCapacity, baseRefillRate, metrics);
  
  return await client.checkRateLimit({
    identifier: userId,
    ...limits
  });
}

Strategy 3: User Tier-Based Limits

Implement different limits for free, premium, and enterprise users:

import { createClient } from 'limitly-sdk';

const client = createClient({ serviceId: 'tiered' });

interface User {
  id: string;
  plan: 'free' | 'pro' | 'enterprise';
  isAdmin?: boolean;
  customLimits?: { capacity: number; refillRate: number };
}

// Base limits per tier
const tierLimits = {
  free: {
    capacity: 100,
    refillRate: 10,
    endpoints: {
      '/api/search': { capacity: 50, refillRate: 5 },
      '/api/export': { capacity: 1, refillRate: 0.1 }  // 1 export per 10 seconds
    }
  },
  pro: {
    capacity: 1000,
    refillRate: 100,
    endpoints: {
      '/api/search': { capacity: 500, refillRate: 50 },
      '/api/export': { capacity: 10, refillRate: 1 }
    }
  },
  enterprise: {
    capacity: 10000,
    refillRate: 1000,
    endpoints: {
      '/api/search': { capacity: 5000, refillRate: 500 },
      '/api/export': { capacity: 100, refillRate: 10 }
    }
  }
};

async function checkTierBasedLimit(
  user: User, 
  endpoint?: string
) {
  // Admins bypass all rate limits
  if (user.isAdmin) {
    return {
      allowed: true,
      limit: Infinity,
      remaining: Infinity,
      tier: 'admin'
    };
  }
  
  // Use custom limits if provided
  if (user.customLimits) {
    return await client.checkRateLimit({
      identifier: user.id,
      ...user.customLimits
    });
  }
  
  // Get tier configuration
  const tierConfig = tierLimits[user.plan];
  
  // Check if endpoint has specific limits
  const endpointLimits = endpoint && tierConfig.endpoints[endpoint]
    ? tierConfig.endpoints[endpoint]
    : null;
  
  const limits = endpointLimits || {
    capacity: tierConfig.capacity,
    refillRate: tierConfig.refillRate
  };
  
  const result = await client.checkRateLimit({
    identifier: `${user.id}:${endpoint || 'default'}`,
    ...limits
  });
  
  return {
    ...result,
    tier: user.plan
  };
}

// Usage
const freeUser: User = { id: 'user-1', plan: 'free' };
const proUser: User = { id: 'user-2', plan: 'pro' };
const enterpriseUser: User = { id: 'user-3', plan: 'enterprise' };

const freeResult = await checkTierBasedLimit(freeUser, '/api/export');
const proResult = await checkTierBasedLimit(proUser, '/api/search');
const enterpriseResult = await checkTierBasedLimit(enterpriseUser);

Strategy 4: Time-of-Day Based Limits

Adjust limits based on time of day or day of week:

import { createClient } from 'limitly-sdk';

const client = createClient({ serviceId: 'time-based' });

interface TimeBasedConfig {
  peakHours: { capacity: number; refillRate: number };
  offPeakHours: { capacity: number; refillRate: number };
  weekend: { capacity: number; refillRate: number };
}

const timeConfig: TimeBasedConfig = {
  peakHours: { capacity: 50, refillRate: 5 },      // Stricter during business hours
  offPeakHours: { capacity: 200, refillRate: 20 }, // More lenient during off-hours
  weekend: { capacity: 300, refillRate: 30 }       // Most lenient on weekends
};

function getTimeBasedLimits(): { capacity: number; refillRate: number } {
  const now = new Date();
  const hour = now.getHours();
  const day = now.getDay(); // 0 = Sunday, 6 = Saturday
  
  // Weekend
  if (day === 0 || day === 6) {
    return timeConfig.weekend;
  }
  
  // Peak hours: 9 AM - 5 PM (9-17)
  if (hour >= 9 && hour < 17) {
    return timeConfig.peakHours;
  }
  
  // Off-peak hours
  return timeConfig.offPeakHours;
}

async function checkTimeBasedLimit(userId: string) {
  const limits = getTimeBasedLimits();
  
  return await client.checkRateLimit({
    identifier: userId,
    ...limits
  });
}

Strategy 5: Combined Strategies

Combine multiple strategies for comprehensive rate limiting:

import { createClient } from 'limitly-sdk';

const client = createClient({ serviceId: 'combined' });

interface RequestContext {
  user: User;
  endpoint: string;
  ip: string;
  country?: string;
}

async function checkCombinedLimit(context: RequestContext) {
  // 1. Get base limits from user tier
  const tierConfig = tierLimits[context.user.plan];
  let limits = {
    capacity: tierConfig.capacity,
    refillRate: tierConfig.refillRate
  };
  
  // 2. Adjust for endpoint
  if (tierConfig.endpoints[context.endpoint]) {
    limits = tierConfig.endpoints[context.endpoint];
  }
  
  // 3. Adjust for time of day
  const timeLimits = getTimeBasedLimits();
  limits.capacity = Math.min(limits.capacity, timeLimits.capacity);
  limits.refillRate = Math.min(limits.refillRate, timeLimits.refillRate);
  
  // 4. Adjust for system load
  const metrics = await getSystemMetrics();
  const adaptiveLimits = calculateAdaptiveLimits(
    limits.capacity,
    limits.refillRate,
    metrics
  );
  
  // 5. Check rate limit
  const identifier = `${context.user.id}:${context.endpoint}:${context.country || 'unknown'}`;
  
  return await client.checkRateLimit({
    identifier,
    ...adaptiveLimits,
    skip: context.user.isAdmin
  });
}