or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdindex.mdmiddleware.mdstores.md
tile.json

middleware.mddocs/

Advanced Middleware Patterns

This document covers advanced usage patterns and integration strategies for express-rate-limit.

Middleware Methods

The rate limiter returns an enhanced middleware with additional methods:

interface RateLimitRequestHandler extends RequestHandler {
  // Reset hit counter for specific client key
  resetKey(key: string): void;
  
  // Get current hit count and reset time for specific client key  
  getKey(key: string): Promise<ClientRateLimitInfo | undefined> | ClientRateLimitInfo | undefined;
}

interface ClientRateLimitInfo {
  totalHits: number;
  resetTime: Date | undefined;
}

Using Middleware Methods

import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100
});

// Reset a specific user's rate limit
app.post("/admin/reset-rate-limit", async (req, res) => {
  const { userId } = req.body;
  await limiter.resetKey(userId);
  res.json({ message: "Rate limit reset successfully" });
});

// Check rate limit status
app.get("/api/rate-limit-status", async (req, res) => {
  const info = await limiter.getKey(req.ip);
  res.json({
    hits: info?.totalHits || 0,
    resetTime: info?.resetTime
  });
});

Request Information

Rate limit information is attached to each request:

interface RateLimitInfo {
  limit: number;        // Maximum requests allowed
  used: number;         // Current request count
  remaining: number;    // Remaining requests in window
  resetTime: Date | undefined; // When the window resets
  key: string;         // The key used for this client
}

Accessing Rate Limit Info

app.use(rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  requestPropertyName: "rateLimit" // Default property name
}));

app.use((req, res, next) => {
  const rateLimitInfo = req.rateLimit;
  
  // Add custom headers
  res.set("X-Requests-Remaining", rateLimitInfo.remaining.toString());
  res.set("X-Requests-Used", rateLimitInfo.used.toString());
  
  // Log high usage
  if (rateLimitInfo.remaining < 10) {
    console.warn(`Client ${rateLimitInfo.key} approaching rate limit`);
  }
  
  next();
});

Custom Key Generation

IP-based Key Generation with Utilities

import { ipKeyGenerator } from "express-rate-limit";

const limiter = rateLimit({
  keyGenerator: (req, res) => {
    return ipKeyGenerator(req.ip); // Uses default IPv6 subnet of 56
  }
});

// Custom IPv6 subnet
const strictLimiter = rateLimit({
  keyGenerator: (req, res) => {
    return ipKeyGenerator(req.ip, 64); // More granular IPv6 grouping
  }
});

// Disable IPv6 subnet grouping
const exactIpLimiter = rateLimit({
  keyGenerator: (req, res) => {
    return ipKeyGenerator(req.ip, false); // Use exact IPv6 addresses
  }
});

Custom Key Generation Patterns

// User-based rate limiting
const userLimiter = rateLimit({
  keyGenerator: (req, res) => {
    return req.user?.id || req.ip; // Use user ID if authenticated, IP otherwise
  }
});

// API key-based rate limiting
const apiLimiter = rateLimit({
  keyGenerator: (req, res) => {
    const apiKey = req.headers["x-api-key"];
    return apiKey || req.ip;
  }
});

// Route-specific rate limiting
const routeLimiter = rateLimit({
  keyGenerator: (req, res) => {
    return `${req.ip}:${req.route.path}`; // Different limits per route
  }
});

// Composite key generation
const compositeLimiter = rateLimit({
  keyGenerator: (req, res) => {
    const userId = req.user?.id;
    const endpoint = req.path;
    return userId ? `user:${userId}:${endpoint}` : `ip:${req.ip}:${endpoint}`;
  }
});

Custom Handlers and Responses

Custom Rate Limit Exceeded Handler

type RateLimitExceededEventHandler = (
  request: Request,
  response: Response,
  next: NextFunction,
  optionsUsed: Options,
) => void;
const customLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  handler: (req, res, next, options) => {
    const rateLimitInfo = req.rateLimit;
    
    // Custom response with detailed information
    res.status(429).json({
      error: "Rate limit exceeded",
      message: "Too many requests from this client",
      limit: rateLimitInfo.limit,
      used: rateLimitInfo.used,
      remaining: rateLimitInfo.remaining,
      resetTime: rateLimitInfo.resetTime,
      retryAfter: Math.ceil((rateLimitInfo.resetTime?.getTime() - Date.now()) / 1000)
    });
    
    // Log rate limit exceeded events
    console.warn(`Rate limit exceeded for ${rateLimitInfo.key}`, {
      ip: req.ip,
      userAgent: req.get("User-Agent"),
      path: req.path,
      timestamp: new Date().toISOString()
    });
  }
});

Conditional Handlers

const conditionalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  handler: (req, res, next, options) => {
    const rateLimitInfo = req.rateLimit;
    
    // Different responses based on client type
    if (req.headers["x-api-key"]) {
      // API client response
      res.status(429).json({
        error: "RATE_LIMIT_EXCEEDED",
        code: 1001,
        retryAfter: Math.ceil((rateLimitInfo.resetTime?.getTime() - Date.now()) / 1000)
      });
    } else {
      // Web client response
      res.status(429).render("rate-limit-exceeded", {
        resetTime: rateLimitInfo.resetTime,
        limit: rateLimitInfo.limit
      });
    }
  }
});

Request Filtering Patterns

Skip Based on Authentication

const authenticatedLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  skip: (req, res) => {
    // Skip rate limiting for authenticated premium users
    return req.user?.plan === "premium";
  }
});

Skip Based on Request Type

const selectiveLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  skip: (req, res) => {
    // Skip rate limiting for health checks and monitoring
    return req.path === "/health" || 
           req.path === "/metrics" ||
           req.headers["user-agent"]?.includes("monitoring");
  }
});

Smart Success/Failure Filtering

const smartLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  skipSuccessfulRequests: true, // Only count failed requests
  requestWasSuccessful: (req, res) => {
    // Custom success logic
    return res.statusCode >= 200 && res.statusCode < 400;
  }
});

// Authentication-specific filtering
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 5,
  skipSuccessfulRequests: true, // Only count failed login attempts  
  requestWasSuccessful: (req, res) => {
    // Success means authentication succeeded
    return res.statusCode === 200 && res.locals.authSuccess;
  }
});

Advanced Integration Patterns

Multiple Rate Limiters

// Global rate limiter
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 1000,
  message: "Too many requests from this IP"
});

// API-specific rate limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  keyGenerator: (req, res) => `api:${req.ip}`,
  message: "API rate limit exceeded"
});

// Authentication rate limiter
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 5,
  keyGenerator: (req, res) => `auth:${req.ip}`,
  skipSuccessfulRequests: true
});

// Apply multiple limiters
app.use(globalLimiter);
app.use("/api", apiLimiter);
app.use("/auth", authLimiter);

Graduated Rate Limiting

// Warn at 80% of limit, block at 100%
const graduatedLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  handler: (req, res, next, options) => {
    res.status(429).json({ error: "Rate limit exceeded" });
  }
});

// Warning middleware (applied before rate limiter)
const warningMiddleware = (req, res, next) => {
  // This would need to check the store directly
  const rateLimitInfo = req.rateLimit;
  if (rateLimitInfo && rateLimitInfo.remaining <= rateLimitInfo.limit * 0.2) {
    res.set("X-Rate-Limit-Warning", "Approaching rate limit");
  }
  next();
};

app.use(graduatedLimiter);
app.use(warningMiddleware);

Dynamic Configuration

const dynamicLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: async (req, res) => {
    // Adjust limits based on system load
    const systemLoad = await getSystemLoad();
    if (systemLoad > 0.8) return 50;  // Reduced limit under high load
    if (systemLoad < 0.3) return 200; // Increased limit under low load
    return 100; // Normal limit
  },
  message: async (req, res) => {
    const systemLoad = await getSystemLoad();
    return systemLoad > 0.8 
      ? "System under high load, please retry later"
      : "Rate limit exceeded";
  }
});

Distributed Rate Limiting

// Using Redis for distributed rate limiting
const distributedLimiter = rateLimit({
  store: new RedisStore(redisClient),
  windowMs: 15 * 60 * 1000,
  limit: 100,
  keyGenerator: (req, res) => {
    // Global key across all instances
    return `global:${req.ip}`;
  }
});

// Per-service rate limiting
const serviceLimiter = rateLimit({
  store: new RedisStore(redisClient),
  windowMs: 15 * 60 * 1000,
  limit: 1000,
  keyGenerator: (req, res) => {
    // Service-specific key
    return `service:${process.env.SERVICE_NAME}:${req.ip}`;
  }
});

Error Handling and Monitoring

Store Error Handling

const resilientLimiter = rateLimit({
  store: externalStore,
  windowMs: 15 * 60 * 1000,
  limit: 100,
  passOnStoreError: true, // Continue serving requests if store fails
});

// Add error monitoring
app.use((error, req, res, next) => {
  if (error.message.includes("rate limit store")) {
    console.error("Rate limit store error:", error);
    // Send alert to monitoring system
    // Fall back to allowing the request
    next();
  } else {
    next(error);
  }
});

Rate Limit Monitoring

const monitoredLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  handler: (req, res, next, options) => {
    // Monitor rate limit violations
    const rateLimitInfo = req.rateLimit;
    
    // Send metrics to monitoring system
    metrics.increment("rate_limit.exceeded", {
      key: rateLimitInfo.key,
      path: req.path,
      method: req.method
    });
    
    // Log detailed information
    logger.warn("Rate limit exceeded", {
      key: rateLimitInfo.key,
      ip: req.ip,
      path: req.path,
      userAgent: req.get("User-Agent"),
      used: rateLimitInfo.used,
      limit: rateLimitInfo.limit
    });
    
    res.status(429).json({ error: "Rate limit exceeded" });
  }
});

Health Check Integration

// Health check endpoint that reports rate limiter status
app.get("/health", async (req, res) => {
  try {
    // Test store connectivity
    await limiter.getKey("health-check");
    
    res.json({
      status: "healthy",
      rateLimiter: {
        store: "connected",
        type: limiter.store.constructor.name
      }
    });
  } catch (error) {
    res.status(503).json({
      status: "unhealthy",
      rateLimiter: {
        store: "disconnected",
        error: error.message
      }
    });
  }
});