Skip to content

High-performance native rate limiter for Node.js capable of processing over 9M+ req/sec

License

Notifications You must be signed in to change notification settings

mehrantsi/hyperlimit

Repository files navigation

HyperLimit

npm version Build and Release Test CodeQL CodeFactor

High-performance native rate limiter for Node.js with lock-free design, optimized for high-throughput applications. Capable of processing over 9 million requests per second in synthetic benchmarks.

Features

  • 🚀 Native C++ implementation for maximum performance
  • 🔒 Lock-free design for high concurrency
  • 🎯 Per-key rate limiting with configurable windows
  • 💾 Memory-efficient implementation
  • 🛡️ Thread-safe operations
  • 🔄 Sliding window support
  • 🎭 Multiple independent rate limiters
  • 📊 Real-time monitoring and statistics
  • 🌐 Redis-based distributed rate limiting
  • 🎛️ Dynamic rate limiting per tenant/API key
  • ⚡ Bypass keys for trusted clients
  • 🎯 Penalty system for abuse prevention
  • 📈 Customizable key generation
  • 🔌 Framework-specific middleware packages

Installation

Choose the package that matches your framework:

# For Express.js
npm install @hyperlimit/express

# For Fastify
npm install @hyperlimit/fastify

# For HyperExpress
npm install @hyperlimit/hyperexpress

# Core package (if you want to build custom middleware)
npm install hyperlimit

Basic Usage

Express.js

const express = require('express');
const rateLimiter = require('@hyperlimit/express');

const app = express();

// Example routes using direct configuration
app.get('/api/public', rateLimiter({
    key: 'public',
    maxTokens: 100,
    window: '1m',
    sliding: true,
    block: '30s'
}), (req, res) => {
    res.json({ message: 'Public API response' });
});

// Protected route with more options
app.get('/api/protected', rateLimiter({
    key: 'protected',
    maxTokens: 5,
    window: '1m',
    sliding: true,
    block: '5m',
    maxPenalty: 3,
    onRejected: (req, res, info) => {
        res.status(429).json({
            error: 'Rate limit exceeded',
            message: 'Please try again later',
            retryAfter: info.retryAfter
        });
    }
}), (req, res) => {
    res.json({ message: 'Protected API response' });
});

// Custom rate limit with bypass keys
app.get('/api/custom', rateLimiter({
    key: 'custom',
    maxTokens: 20,
    window: '30s',
    sliding: true,
    block: '1m',
    keyGenerator: req => `${req.ip}-${req.query.userId}`,
    bypassHeader: 'X-Custom-Key',
    bypassKeys: ['special-key']
}), (req, res) => {
    res.json({ message: 'Custom API response' });
});

Fastify

const fastify = require('fastify')();
const rateLimiter = require('@hyperlimit/fastify');

fastify.get('/api/public', {
    preHandler: rateLimiter({
        key: 'public',
        maxTokens: 100,
        window: '1m',
        sliding: true,
        block: '30s'
    }),
    handler: async (request, reply) => {
        return { message: 'Public API response' };
    }
});

// Protected route with more options
fastify.get('/api/protected', {
    preHandler: rateLimiter({
        key: 'protected',
        maxTokens: 5,
        window: '1m',
        sliding: true,
        block: '5m',
        maxPenalty: 3,
        onRejected: (request, reply, info) => {
            reply.code(429).send({
                error: 'Rate limit exceeded',
                message: 'Please try again later',
                retryAfter: info.retryAfter
            });
        }
    }),
    handler: async (request, reply) => {
        return { message: 'Protected API response' };
    }
});

// Custom rate limit with bypass keys
fastify.get('/api/custom', {
    preHandler: rateLimiter({
        key: 'custom',
        maxTokens: 20,
        window: '30s',
        sliding: true,
        block: '1m',
        keyGenerator: req => `${req.ip}-${req.query.userId}`,
        bypassHeader: 'X-Custom-Key',
        bypassKeys: ['special-key']
    }),
    handler: async (request, reply) => {
        return { message: 'Custom API response' };
    }
});

HyperExpress

const HyperExpress = require('hyper-express');
const rateLimiter = require('@hyperlimit/hyperexpress');

const app = new HyperExpress.Server();

app.get('/api/public', rateLimiter({
    key: 'public',
    maxTokens: 100,
    window: '1m',
    sliding: true,
    block: '30s'
}), (req, res) => {
    res.json({ message: 'Public API response' });
});

// Protected route with more options
app.get('/api/protected', rateLimiter({
    key: 'protected',
    maxTokens: 5,
    window: '1m',
    sliding: true,
    block: '5m',
    maxPenalty: 3,
    onRejected: (req, res, info) => {
        res.status(429);
        res.json({
            error: 'Rate limit exceeded',
            message: 'Please try again later',
            retryAfter: info.retryAfter
        });
    }
}), (req, res) => {
    res.json({ message: 'Protected API response' });
});

// Custom rate limit with bypass keys
app.get('/api/custom', rateLimiter({
    key: 'custom',
    maxTokens: 20,
    window: '30s',
    sliding: true,
    block: '1m',
    keyGenerator: req => `${req.ip}-${req.query.userId}`,
    bypassHeader: 'X-Custom-Key',
    bypassKeys: ['special-key']
}), (req, res) => {
    res.json({ message: 'Custom API response' });
});

Core Package Usage

For custom middleware or direct usage:

const { HyperLimit } = require('hyperlimit');

// Create a rate limiter instance
const limiter = new HyperLimit({
    bucketCount: 16384  // Optional: number of hash table buckets (default: 16384)
});

// Create a limiter for a specific endpoint/feature
limiter.createLimiter(
    'api:endpoint1',    // Unique identifier for this limiter
    100,               // maxTokens: Maximum requests allowed
    60000,             // window: Time window in milliseconds
    true,              // sliding: Use sliding window
    30000,             // block: Block duration in milliseconds
    5                  // maxPenalty: Maximum penalty points
);

// Example usage in custom middleware
function customRateLimiter(options = {}) {
    const limiter = new HyperLimit();
    const defaultKey = 'default';
    
    // Create the limiter with options
    limiter.createLimiter(
        defaultKey,
        options.maxTokens || 100,
        typeof options.window === 'string' 
            ? parseTimeString(options.window) 
            : (options.window || 60000),
        options.sliding !== false,
        typeof options.block === 'string'
            ? parseTimeString(options.block)
            : (options.block || 0),
        options.maxPenalty || 0
    );

    // Return middleware function
    return function(req, res, next) {
        const key = options.keyGenerator?.(req) || req.ip;
        const allowed = limiter.tryRequest(key);

        if (!allowed) {
            const info = limiter.getRateLimitInfo(key);
            return res.status(429).json({
                error: 'Too many requests',
                retryAfter: Math.ceil(info.reset / 1000)
            });
        }

        // Attach limiter to request for later use
        req.rateLimit = { limiter, key };
        next();
    };
}

// Helper to parse time strings (e.g., '1m', '30s')
function parseTimeString(timeStr) {
    const units = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
    const match = timeStr.match(/^(\d+)([a-z]+)$/i);
    if (!match) return parseInt(timeStr, 10);
    const [, num, unit] = match;
    return parseInt(num, 10) * (units[unit] || units.ms);
}

Advanced Features

1. Tenant-Based Rate Limiting

Perfect for SaaS applications with different tiers of service:

// Simulating a database of tenant configurations
const tenantConfigs = new Map();
const tenantLimiters = new Map();

// Example tenant configurations
tenantConfigs.set('tenant1-key', {
    name: 'Basic Tier',
    endpoints: {
        '/api/data': { maxTokens: 5, window: '10s' },
        '/api/users': { maxTokens: 2, window: '10s' },
        '*': { maxTokens: 10, window: '60s' } // Default for unspecified endpoints
    }
});

tenantConfigs.set('tenant2-key', {
    name: 'Premium Tier',
    endpoints: {
        '/api/data': { maxTokens: 20, window: '10s' },
        '/api/users': { maxTokens: 10, window: '10s' },
        '*': { maxTokens: 50, window: '60s' }
    }
});

// Helper to get or create rate limiter for tenant+endpoint
function getTenantLimiter(tenantKey, endpoint) {
    const cacheKey = `${tenantKey}:${endpoint}`;
    
    if (tenantLimiters.has(cacheKey)) {
        return tenantLimiters.get(cacheKey);
    }

    const tenantConfig = tenantConfigs.get(tenantKey);
    if (!tenantConfig) {
        return null;
    }

    // Get endpoint specific config or fallback to default
    const config = tenantConfig.endpoints[endpoint] || tenantConfig.endpoints['*'];
    if (!config) {
        return null;
    }

    const limiter = rateLimiter({
        maxTokens: config.maxTokens,
        window: config.window,
        keyGenerator: (req) => req.headers['x-api-key']
    });

    tenantLimiters.set(cacheKey, limiter);
    return limiter;
}

// Tenant authentication middleware
function authenticateTenant(req, res, next) {
    const apiKey = req.headers['x-api-key'];
    if (!apiKey) {
        return res.status(401).json({ error: 'API key required' });
    }

    const config = tenantConfigs.get(apiKey);
    if (!config) {
        return res.status(401).json({ error: 'Invalid API key' });
    }

    req.tenant = {
        apiKey,
        config
    };
    next();
}

// Dynamic rate limiting middleware
function dynamicRateLimit(req, res, next) {
    const limiter = getTenantLimiter(req.tenant.apiKey, req.path);
    if (!limiter) {
        return res.status(500).json({ error: 'Rate limiter configuration error' });
    }
    
    return limiter(req, res, next);
}

// Apply tenant authentication to all routes
app.use(authenticateTenant);

// API endpoints with dynamic rate limiting
app.get('/api/data', dynamicRateLimit, (req, res) => {
    res.json({
        message: 'Data endpoint',
        tenant: req.tenant.config.name,
        path: req.path
    });
});

2. Distributed Rate Limiting

For applications running across multiple servers:

const { HyperLimit } = require('hyperlimit');

// Create HyperLimit instance with Redis support
const limiter = new HyperLimit({
    bucketCount: 16384,
    redis: {
        host: 'localhost',
        port: 6379,
        prefix: 'rl:'
    }
});

// Configure rate limiter with a distributed key for global coordination
limiter.createLimiter(
    'api:endpoint1',      // Local identifier
    100,                  // Total allowed requests across all servers
    60000,               // 1 minute window
    true,                // Use sliding window
    0,                   // No block duration
    0,                   // No penalties
    'api:endpoint1:global' // Distributed key for Redis
);

// Use in your application
app.get('/api/endpoint', (req, res) => {
    const allowed = limiter.tryRequest('api:endpoint1');
    if (!allowed) {
        const info = limiter.getRateLimitInfo('api:endpoint1');
        return res.status(429).json({
            error: 'Rate limit exceeded',
            retryAfter: Math.ceil(info.reset / 1000)
        });
    }
    res.json({ message: 'Success' });
});

// Or use with middleware
app.get('/api/endpoint', rateLimiter({
    key: 'api:endpoint1',
    maxTokens: 100,
    window: '1m',
    sliding: true,
    redis: {
        host: 'localhost',
        port: 6379,
        prefix: 'rl:'
    }
}), (req, res) => {
    res.json({ message: 'Success' });
});

When Redis is configured:

  • Rate limits are synchronized across all application instances
  • Tokens are stored and managed in Redis
  • Automatic fallback to local rate limiting if Redis is unavailable
  • Atomic operations ensure consistency
  • Minimal latency overhead

Redis configuration options:

{
    host: 'localhost',      // Redis host
    port: 6379,            // Redis port
    password: 'optional',   // Redis password
    db: 0,                 // Redis database number
    prefix: 'rl:',         // Key prefix for rate limit data
    connectTimeout: 10000,  // Connection timeout in ms
    maxRetriesPerRequest: 3 // Max retries per request
}

3. Penalty System

Handle abuse and violations:

app.post('/api/sensitive', limiter, (req, res) => {
    if (violationDetected) {
        // Add penalty points
        req.rateLimit.limiter.addPenalty(req.rateLimit.key, 2);
        return res.status(400).json({ error: 'Violation detected' });
    }
    
    // Later, can remove penalties
    req.rateLimit.limiter.removePenalty(req.rateLimit.key, 1);
});

4. Monitoring and Statistics

Track rate limiting status:

app.get('/status', (req, res) => {
    const info = req.rateLimit.limiter.getRateLimitInfo(req.rateLimit.key);
    res.json({
        limit: info.limit,
        remaining: info.remaining,
        reset: info.reset,
        blocked: info.blocked
    });
});

5. Redis Integration Details

When Redis is configured:

  • Rate limits are synchronized across all application instances
  • Tokens are stored and managed in Redis
  • Automatic fallback to local rate limiting if Redis is unavailable
  • Atomic operations ensure consistency
  • Minimal latency overhead

Redis configuration options:

{
    host: 'localhost',      // Redis host
    port: 6379,            // Redis port
    password: 'optional',   // Redis password
    db: 0,                 // Redis database number
    prefix: 'rl:',         // Key prefix for rate limit data
    connectTimeout: 10000,  // Connection timeout in ms
    maxRetriesPerRequest: 3 // Max retries per request
}

Configuration Options

interface RateLimiterOptions {
    // Core Options
    maxTokens: number;      // Maximum requests allowed
    window: string|number;  // Time window (e.g., '1m', '30s', or milliseconds)
    sliding?: boolean;      // Use sliding window (default: true)
    block?: string|number;  // Block duration after limit exceeded
    
    // Advanced Options
    maxPenalty?: number;     // Maximum penalty points
    bypassHeader?: string;   // Header for bypass keys
    bypassKeys?: string[];   // List of bypass keys
    keyGenerator?: (req) => string;  // Custom key generation
    
    // Distributed Options
    redis?: Redis;           // Redis client for distributed mode
    
    // Response Handling
    onRejected?: (req, res, info) => void;  // Custom rejection handler
}

Response Headers

The middleware automatically sets these headers:

  • X-RateLimit-Limit: Maximum allowed requests
  • X-RateLimit-Remaining: Remaining requests in window
  • X-RateLimit-Reset: Seconds until limit resets

Performance Benchmarks

HyperLimit achieves exceptional performance through:

  • Native C++ implementation
  • Lock-free atomic operations
  • No external dependencies
  • Efficient token bucket algorithm
  • Minimal memory footprint
Test Type Requests/sec Latency (ms)
Single Key ~9.1M 0.11
Multi-Key ~7.5M 0.13
Concurrent ~3.2M 0.31

Detailed Benchmark Results

Tests performed on a MacBook Pro with Apple M3 Max, 64GB RAM. Each test measures:

  • Single Key: Processing 1,000,000 requests through a single key
  • Multi-Key: Processing 100,000 requests with unique keys
  • Concurrent: Processing 100,000 concurrent requests with unique keys

Small Hash Table (1K)

Test Type HyperLimit Rate Limiter Flexible Express Rate Limit Req/sec (HyperLimit)
Single Key 109.887ms 176.971ms 8.359s ~9.1M
Multi-Key 14.747ms 90.725ms 906.47ms ~6.8M
Concurrent 30.99ms 96.715ms 995.496ms ~3.2M

Default Hash Table (16K)

Test Type HyperLimit Rate Limiter Flexible Express Rate Limit Req/sec (HyperLimit)
Single Key 106.497ms 174.073ms 9.338s ~9.4M
Multi-Key 13.311ms 84.9ms 985.935ms ~7.5M
Concurrent 34.857ms 101.525ms 986.597ms ~2.9M

Large Hash Table (64K)

Test Type HyperLimit Rate Limiter Flexible Express Rate Limit Req/sec (HyperLimit)
Single Key 109.467ms 184.259ms 9.374s ~9.1M
Multi-Key 12.12ms 77.029ms 935.926ms ~8.3M
Concurrent 34.236ms 90.011ms 1.079s ~2.9M

Key findings:

  • Up to 85x faster than Express Rate Limit
  • Up to 7x faster than Rate Limiter Flexible
  • Consistent performance across different hash table sizes
  • Exceptional multi-key and concurrent performance
  • Sub-millisecond latency in most scenarios

Note: These are synthetic benchmarks measuring raw performance without network overhead or real-world conditions. Actual performance will vary based on your specific use case and environment.

Examples

Check out the examples directory for:

  • Basic rate limiting setups
  • Tenant-based configurations
  • Distributed rate limiting
  • Penalty system implementation
  • Monitoring and statistics
  • Custom key generation
  • Framework-specific implementations

Best Practices

  1. Key Generation:

    • Use meaningful keys (e.g., ${req.ip}-${req.path})
    • Consider user identity for authenticated routes
    • Combine multiple factors for granular control
  2. Window Selection:

    • Use sliding windows for smooth rate limiting
    • Match window size to endpoint sensitivity
    • Consider user experience when setting limits
  3. Distributed Setup:

    • Enable Redis for multi-server deployments
    • Set appropriate prefix to avoid key collisions
    • Monitor Redis connection health
  4. Penalty System:

    • Start with small penalties
    • Implement gradual penalty removal
    • Log penalty events for analysis
  5. Monitoring:

    • Regularly check rate limit status
    • Monitor bypass key usage
    • Track penalty patterns

Building from Source

# Clone the repository
git clone https://github.com/mehrantsi/hyperlimit.git

# Install dependencies
cd hyperlimit
npm install

# Build
npm run build

# Run tests
npm test

# Run examples
npm run example:express
npm run example:fastify
npm run example:hyperexpress

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. See CONTRIBUTING.md for more information.

License

MIT License - see LICENSE file for details