@samahuja642/rate-limiter
v1.0.0
Published
A flexible, customizable rate limiting package for Node.js with multiple algorithms and storage backends
Maintainers
Readme
@samahuja642/rate-limiter
A flexible, production-ready rate limiting package for Node.js with multiple algorithms, storage backends, and framework integrations.
Features
- 🚀 Multiple Algorithms: Fixed Window, Leaky Bucket
- 💾 Flexible Storage: In-memory (default) and Redis support
- 🔌 Framework Integration: Express and generic middleware
- 📘 TypeScript: Full type definitions included
- ⚡ High Performance: Minimal overhead, optimized for production
- 🛡️ Battle-tested: Comprehensive test coverage
- 🎯 Customizable: Extensive configuration options
Installation
npm install @samahuja642/rate-limiterFor Redis support:
npm install @samahuja642/rate-limiter redis
# or
npm install @samahuja642/rate-limiter ioredisQuick Start
Express with Fixed Window (In-Memory)
const express = require('express');
const { FixedWindow, MemoryStorage, createExpressMiddleware } = require('@samahuja642/rate-limiter');
const app = express();
// Create storage and rate limiter
const storage = new MemoryStorage();
const limiter = new FixedWindow(storage, {
maxRequests: 100, // Max requests
windowMs: 60000 // Per minute
});
// Apply to all routes
app.use(createExpressMiddleware({ limiter }));
app.get('/api/data', (req, res) => {
res.json({ message: 'Success!' });
});
app.listen(3000);Express with Redis (Distributed)
const express = require('express');
const redis = require('redis');
const { FixedWindow, RedisStorage, createExpressMiddleware } = require('@samahuja642/rate-limiter');
const app = express();
// Create Redis client and storage
const redisClient = redis.createClient({ url: 'redis://localhost:6379' });
await redisClient.connect();
const storage = new RedisStorage(redisClient);
const limiter = new FixedWindow(storage, {
maxRequests: 100,
windowMs: 60000 // 100 requests per minute
});
app.use(createExpressMiddleware({ limiter }));
app.listen(3000);Algorithms
Comparison Table
| Algorithm | Bursts Allowed | Memory Usage | Accuracy | Best For | |-----------|---------------|--------------|----------|----------| | Fixed Window | ⚠️ At boundaries | Very Low | Fair | Simple rate limiting | | Leaky Bucket | ❌ No | Low | Good | Traffic shaping |
Fixed Window
Simple counter that resets at fixed intervals.
const limiter = new FixedWindow(storage, {
maxRequests: 100,
windowMs: 60000 // 100 requests per minute
});Use case: Simple rate limiting, low-memory environments
Leaky Bucket
Smooths traffic to a constant output rate.
const limiter = new LeakyBucket(storage, {
capacity: 100,
leakRate: 10 // 10 requests per second
});Use case: Upstream rate limiting, traffic shaping
Storage Backends
In-Memory Storage
Default storage for single-instance applications.
const { MemoryStorage } = require('@samahuja642/rate-limiter');
const storage = new MemoryStorage(300000); // Cleanup every 5 minutesPros: Fast, no external dependencies
Cons: Not suitable for multi-instance deployments
Redis Storage
For distributed applications across multiple instances.
const redis = require('redis');
const { RedisStorage } = require('@samahuja642/rate-limiter');
const client = redis.createClient({ url: 'redis://localhost:6379' });
await client.connect();
const storage = new RedisStorage(client, 'myapp:ratelimit:');Pros: Distributed, persistent, scalable
Cons: Requires Redis server
Custom Storage
Implement the Storage interface for custom backends:
interface Storage {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
increment(key: string, amount?: number): Promise<number>;
delete(key: string): Promise<void>;
}Framework Integration
Express
const { createExpressMiddleware } = require('@samahuja642/rate-limiter');
app.use(createExpressMiddleware({
limiter,
keyGenerator: (req) => req.ip,
onLimitReached: (req, res) => {
res.status(429).json({ error: 'Too many requests' });
},
skip: (req) => req.path === '/health',
cost: (req) => req.path.includes('/expensive') ? 5 : 1
}));Generic/Custom Framework
const { createGenericRateLimiter } = require('@samahuja642/rate-limiter');
const rateLimiter = createGenericRateLimiter({
limiter,
keyExtractor: (context) => context.userId,
onSuccess: (context, result) => {
context.setHeader('X-RateLimit-Remaining', result.remaining);
},
onFailure: (context, result) => {
context.sendError(429, 'Rate limit exceeded');
}
});
// Use in your custom framework
const allowed = await rateLimiter(context);Advanced Usage
Different Limits per Route
const authLimiter = new FixedWindow(storage, { maxRequests: 5, windowMs: 60000 });
const apiLimiter = new FixedWindow(storage, { maxRequests: 100, windowMs: 60000 });
app.post('/auth/login', createExpressMiddleware({ limiter: authLimiter }), loginHandler);
app.get('/api/data', createExpressMiddleware({ limiter: apiLimiter }), dataHandler);User-Based Rate Limiting
app.use(createExpressMiddleware({
limiter,
keyGenerator: (req) => {
// Rate limit by authenticated user ID
return req.user?.id || req.ip;
}
}));Tiered Rate Limiting
app.use(createExpressMiddleware({
limiter,
cost: (req) => {
// Premium users consume fewer tokens
if (req.user?.tier === 'premium') return 0.5;
if (req.user?.tier === 'free') return 2;
return 1;
}
}));Custom Response Headers
app.use(createExpressMiddleware({
limiter,
onLimitReached: (req, res) => {
res.status(429)
.set('X-Custom-Header', 'Rate limited')
.json({
error: 'Too Many Requests',
retryAfter: res.getHeader('Retry-After'),
message: 'Please slow down your requests'
});
}
}));API Reference
Rate Limiter Methods
All rate limiters implement:
// Consume tokens/requests
consume(key: string, tokens?: number): Promise<RateLimitResult>
// Get status without consuming
getStatus(key: string): Promise<RateLimitResult>
// Reset limit for a key
reset(key: string): Promise<void>RateLimitResult
interface RateLimitResult {
allowed: boolean; // Whether request is allowed
remaining: number; // Tokens/requests remaining
limit: number; // Maximum limit
resetTime: number; // Unix timestamp when limit resets
retryAfter?: number; // Seconds to wait (when allowed is false)
}HTTP Headers
Standard rate limit headers are automatically set:
X-RateLimit-Limit: Maximum requests allowedX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resetsRetry-After: Seconds to wait before retrying (when limit exceeded)
Testing
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Watch mode
npm run test:watchBest Practices
Choose the Right Algorithm
- Use Fixed Window for simple cases
- Use Leaky Bucket for traffic shaping
Use Redis for Production
- In-memory storage doesn't work across multiple instances
- Redis provides persistence and distribution
Set Appropriate Limits
- Start conservative, increase based on monitoring
- Different limits for different endpoints
- Consider user tiers (free vs premium)
Monitor and Adjust
- Track rate limit hits
- Adjust limits based on actual usage patterns
- Log when limits are exceeded
Fail Open on Errors
- The middleware fails open by default (allows requests on errors)
- Prevents outages due to storage issues
Examples
See the examples/ directory for complete working examples:
express-basic.js- Basic Express integrationexpress-redis.js- Express with Redis and multiple algorithmscustom-storage.js- Custom storage implementation
License
MIT © Samarth Ahuja
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📖 Documentation: GitHub Wiki
