HyperLimit

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
- 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
- Distributed rate limiting (Redis or NATS)
- 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:
npm install @hyperlimit/express
npm install @hyperlimit/fastify
npm install @hyperlimit/hyperexpress
npm install hyperlimit
Basic Usage
Express.js
const express = require('express');
const rateLimiter = require('@hyperlimit/express');
const app = express();
app.get('/api/public', rateLimiter({
key: 'public',
maxTokens: 100,
window: '1m',
sliding: true,
block: '30s'
}), (req, res) => {
res.json({ message: 'Public API response' });
});
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' });
});
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' });
});
app.get('/api/distributed', rateLimiter({
key: 'distributed',
maxTokens: 100,
window: '1m',
nats: {
servers: 'nats://localhost:4222',
bucket: 'rate-limits',
prefix: 'rl_'
}
}), (req, res) => {
res.json({ message: 'Distributed 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' };
}
});
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' };
}
});
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' };
}
});
fastify.get('/api/distributed', {
preHandler: rateLimiter({
key: 'distributed',
maxTokens: 100,
window: '1m',
nats: {
servers: 'nats://localhost:4222',
bucket: 'rate-limits',
prefix: 'rl_'
}
}),
handler: async (request, reply) => {
return { message: 'Distributed 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' });
});
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' });
});
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' });
});
app.get('/api/distributed', rateLimiter({
key: 'distributed',
maxTokens: 100,
window: '1m',
nats: {
servers: 'nats://localhost:4222',
bucket: 'rate-limits',
prefix: 'rl_'
}
}), (req, res) => {
res.json({ message: 'Distributed API response' });
});
Core Package Usage
For custom middleware or direct usage:
const { HyperLimit } = require('hyperlimit');
const limiter = new HyperLimit({
bucketCount: 16384
});
limiter.createLimiter(
'api:endpoint1',
100,
60000,
true,
30000,
5
);
function customRateLimiter(options = {}) {
const limiter = new HyperLimit();
const defaultKey = 'default';
limiter.createLimiter(
defaultKey,
options.maxTokens || 100,
typeof options.window === 'string'
? parseDuration(options.window)
: (options.window || 60000),
options.sliding !== false,
typeof options.block === 'string'
? parseDuration(options.block)
: (options.block || 0),
options.maxPenalty || 0
);
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)
});
}
req.rateLimit = { limiter, key };
next();
};
}
function parseDuration(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. Dynamic Configuration with configResolver
Use configResolver
for dynamic, per-request rate limit configuration. This is ideal for multi-tenant applications where different clients have different rate limits:
const express = require('express');
const rateLimit = require('@hyperlimit/express');
app.use(rateLimit({
keyGenerator: (req) => req.headers['x-api-key'] || 'anonymous',
configResolver: (apiKey) => {
const userTier = getUserTier(apiKey);
switch(userTier) {
case 'premium':
return {
maxTokens: 1000,
window: '1h',
sliding: true
};
case 'basic':
return {
maxTokens: 100,
window: '15m',
sliding: true
};
default:
return {
maxTokens: 10,
window: '1h',
sliding: true
};
}
},
nats: {
servers: 'nats://localhost:4222',
bucket: 'rate-limits',
prefix: 'rl_'
}
}));
The configResolver
is called with the key from keyGenerator
and should return a configuration object. Return null
or a config with maxTokens: 0
to deny access.
2. Tenant-Based Rate Limiting
Perfect for SaaS applications with different tiers of service:
const tenantConfigs = new Map();
const tenantLimiters = new Map();
tenantConfigs.set('tenant1-key', {
name: 'Basic Tier',
endpoints: {
'/api/data': { maxTokens: 5, window: '10s' },
'/api/users': { maxTokens: 2, window: '10s' },
'*': { maxTokens: 10, window: '60s' }
}
});
tenantConfigs.set('tenant2-key', {
name: 'Premium Tier',
endpoints: {
'/api/data': { maxTokens: 20, window: '10s' },
'/api/users': { maxTokens: 10, window: '10s' },
'*': { maxTokens: 50, window: '60s' }
}
});
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;
}
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;
}
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();
}
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);
}
app.use(authenticateTenant);
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');
const limiter = new HyperLimit({
bucketCount: 16384,
redis: {
host: 'localhost',
port: 6379,
prefix: 'rl:'
}
});
limiter.createLimiter(
'api:endpoint1',
100,
60000,
true,
0,
0,
'api:endpoint1:global'
);
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' });
});
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',
port: 6379,
password: 'optional',
db: 0,
prefix: 'rl:',
connectTimeout: 10000,
maxRetriesPerRequest: 3
}
3. Penalty System
Handle abuse and violations:
app.post('/api/sensitive', limiter, (req, res) => {
if (violationDetected) {
req.rateLimit.limiter.addPenalty(req.rateLimit.key, 2);
return res.status(400).json({ error: 'Violation detected' });
}
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',
port: 6379,
password: 'optional',
db: 0,
prefix: 'rl:',
connectTimeout: 10000,
maxRetriesPerRequest: 3
}
6. NATS Integration (Alternative to Redis)
HyperLimit also supports NATS as a distributed storage backend, offering:
- Lightweight, high-performance messaging
- Built-in persistence with JetStream
- Native support for distributed architectures
- Lower operational overhead than Redis
- Built-in security features (TLS, authentication)
Prerequisites:
-
NATS server with JetStream enabled
- Quick start:
docker run -p 4222:4222 nats:latest -js
- The
-js
flag is required to enable JetStream for KV storage
-
NATS C client library (cnats) - REQUIRED
- macOS:
brew install cnats
- Ubuntu/Debian:
apt-get install libnats-dev
- Windows:
vcpkg install cnats
Important: The cnats library must be installed on your system for NATS integration to work. HyperLimit uses dynamic library loading to interface with NATS.
NATS configuration:
const limiter = new HyperLimit({
bucketCount: 16384,
nats: {
servers: 'nats://localhost:4222',
bucket: 'rate-limits',
prefix: 'rl_',
credentials: './nats.creds'
}
});
NATS configuration options:
{
servers: string | string[],
bucket: string,
prefix: string,
credentials?: string
}
Example with NATS cluster:
const limiter = new HyperLimit({
bucketCount: 16384,
nats: {
servers: [
'nats://nats1.example.com:4222',
'nats://nats2.example.com:4222',
'nats://nats3.example.com:4222'
],
bucket: 'rate-limits',
prefix: 'rl_'
}
});
Configuration Options
interface RateLimiterOptions {
maxTokens: number;
window: string|number;
sliding?: boolean;
block?: string|number;
maxPenalty?: number;
bypassHeader?: string;
bypassKeys?: string[];
keyGenerator?: (req) => string;
redis?: Redis;
nats?: NatsOptions;
onRejected?: (req, res, info) => void;
configResolver?: (key: string) => {
maxTokens: number;
window: string;
sliding?: boolean;
block?: string;
maxPenalty?: number;
} | null;
}
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
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)
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)
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)
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
-
Key Generation:
- Use meaningful keys (e.g.,
${req.ip}-${req.path}
)
- Consider user identity for authenticated routes
- Combine multiple factors for granular control
-
Window Selection:
- Use sliding windows for smooth rate limiting
- Match window size to endpoint sensitivity
- Consider user experience when setting limits
-
Distributed Setup:
- Enable Redis for multi-server deployments
- Set appropriate prefix to avoid key collisions
- Monitor Redis connection health
-
Penalty System:
- Start with small penalties
- Implement gradual penalty removal
- Log penalty events for analysis
-
Monitoring:
- Regularly check rate limit status
- Monitor bypass key usage
- Track penalty patterns
Prerequisites for Distributed Rate Limiting
Redis (Option 1)
- Redis server (any version supporting Lua scripts)
- Install:
docker run -p 6379:6379 redis:latest
NATS (Option 2)
- NATS server with JetStream enabled
- Install:
docker run -p 4222:4222 nats:latest -js
- C client library (libnats) - REQUIRED:
- macOS:
brew install cnats
- Ubuntu/Debian:
apt-get install libnats-dev
- Windows: Use vcpkg
vcpkg install cnats
Troubleshooting
NATS Integration Issues
"NATS connection failed" Error
- Verify cnats is installed: Run
brew list cnats
(macOS) or dpkg -l libnats-dev
(Ubuntu)
- Check NATS server is running:
nats server check
- Ensure JetStream is enabled: The server must be started with
-js
flag
"Failed to create/bind to KV store" Error
This generic error can occur for several reasons:
-
JetStream permissions: Your NATS user may lack permissions to create buckets. Try:
- Creating the bucket manually:
nats kv add rate-limits --ttl=1h
- Or ensure your user has JetStream admin permissions
-
JetStream initialization: After starting NATS, wait a few seconds before connecting
-
Bucket conflicts: Delete and recreate the bucket:
nats kv rm rate-limits -f
nats kv add rate-limits --ttl=1h
-
Connection issues: Test with NATS CLI:
nats kv put rate-limits test "value"
nats kv get rate-limits test
Dynamic Library Loading Issues
If HyperLimit can't find the cnats library:
- macOS: Ensure
/usr/local/lib
or /opt/homebrew/lib
is in your library path
- Linux: Run
sudo ldconfig
after installing libnats-dev
- Windows: Ensure nats.dll is in your PATH
Building from Source
git clone https://github.com/mehrantsi/hyperlimit.git
cd hyperlimit
npm install
npm run build
npm test
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