cf-rate-limit
v1.0.1
Published
Professional rate limiting middleware for Hono on Cloudflare Workers with KV and Durable Objects support
Maintainers
Readme
CF Rate Limit
Production-ready rate limiting middleware for Hono on Cloudflare Workers
Choose between KV Storage (simple & fast) or Durable Objects (precise & accurate) based on your requirements.
📦 Installation
npm install cf-rate-limit⚡ Quick Start
KV Storage (recommended for most cases):
import { rateLimit } from 'cf-rate-limit';
app.use('/api/*', rateLimit({
window: 60, max: 100, kv: 'RATE_LIMIT_KV'
}));Durable Objects (for high precision):
import { rateLimitDO, RateLimiterDO } from 'cf-rate-limit';
app.use('/api/*', rateLimitDO({
window: 60, max: 100, do: 'RATE_LIMITER'
}));🎯 Comparison Guide
| Feature | KV Storage | Durable Objects | |---------|----------------|---------------------| | Algorithm | Fixed Window | Sliding Window | | Accuracy | Good ✅ | Perfect ✅✅ | | Performance | Faster ⚡⚡ | Fast ⚡ | | Cost | Lower 💰 | Higher 💰💰 | | Setup | Simple 🟢 | Medium 🟡 | | Best For | Most APIs | High-precision APIs |
Decision Matrix:
- Use KV Storage → Public APIs, mobile apps, startups (80% of cases)
- Use Durable Objects → Payment systems, financial services, enterprise compliance
🔥 Method 1: KV Storage
Recommended for most use cases
⚡ Basic Usage
import { Hono } from 'hono';
import { rateLimit } from 'cf-rate-limit';
type Bindings = {
RATE_LIMIT_KV: KVNamespace;
};
const app = new Hono<{ Bindings: Bindings }>();
// 🎯 Apply rate limiting
app.use('/api/*', rateLimit({
window: 60, // 60 seconds
max: 100, // 100 requests
kv: 'RATE_LIMIT_KV', // KV binding name
}));
app.get('/api/data', (c) => c.json({ message: 'Hello!' }));
export default app;📋 Configuration
wrangler.toml setup:
name = "my-app"
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "your-kv-namespace-id"🛠️ API Reference
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| window | number | - | Time window (1-86400 seconds) |
| max | number | - | Max requests per window (1-1M) |
| kv | string | - | KV binding name |
| keyMaker | function | IP-based | Custom key generator |
| message | string | "Too many requests" | Error message |
💡 Advanced Examples
🔑 Custom Key Strategy
app.use('/api/*', rateLimit({
window: 60,
max: 1000,
kv: 'RATE_LIMIT_KV',
keyMaker: (c) => {
const userId = c.req.header('x-user-id');
return userId ? `user:${userId}` : `ip:${c.req.header('cf-connecting-ip')}`;
},
}));🎚️ Multiple Rate Limits
// 🔓 Public endpoints: 100/hour
app.use('/api/public/*', rateLimit({
window: 3600, max: 100, kv: 'RATE_LIMIT_KV'
}));
// 🔐 Auth endpoints: 1000/hour
app.use('/api/auth/*', rateLimit({
window: 3600, max: 1000, kv: 'RATE_LIMIT_KV'
}));
// ⚡ Burst protection: 10/minute
app.use('/api/upload/*', rateLimit({
window: 60, max: 10, kv: 'RATE_LIMIT_KV'
}));📊 Response Headers
Rate limiter automatically adds headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1699123456
X-RateLimit-Window: 45⚡ Method 2: Durable Objects
High precision rate limiting
🎯 Basic Usage
import { Hono } from 'hono';
import { rateLimitDO, RateLimiterDO } from 'cf-rate-limit';
type Bindings = {
RATE_LIMITER: DurableObjectNamespace;
};
const app = new Hono<{ Bindings: Bindings }>();
// 🎯 Apply precise rate limiting
app.use('/api/*', rateLimitDO({
window: 60, // 60 seconds sliding window
max: 100, // 100 requests exactly
do: 'RATE_LIMITER', // DO binding name
headers: true, // Add rate limit headers
}));
app.get('/api/data', (c) => c.json({ message: 'Hello!' }));
// ⚠️ Important: Export DO class
export { RateLimiterDO };
export default app;📋 Configuration
wrangler.toml setup:
name = "my-app"
# Durable Objects configuration
[[durable_objects.bindings]]
name = "RATE_LIMITER"
class_name = "RateLimiterDO"
[[migrations]]
tag = "v1"
new_classes = ["RateLimiterDO"]🛠️ API Reference
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| window | number | - | Sliding window (1-86400 seconds) |
| max | number | - | Max requests in window (1-1M) |
| do | string | - | Durable Object binding name |
| keyMaker | function | IP-based | Custom key generator |
| message | string | "Too many requests" | Error message |
| headers | boolean | true | Add rate limit headers |
💡 Advanced Examples
🔑 Custom Key with DO
app.use('/api/*', rateLimitDO({
window: 300, // 5 minutes
max: 1000,
do: 'RATE_LIMITER',
keyMaker: (c) => {
const apiKey = c.req.header('x-api-key');
return apiKey ? `api:${apiKey}` : `ip:${c.req.header('cf-connecting-ip')}`;
},
}));🎚️ Tiered Rate Limiting
// 🥉 Bronze: 100/hour
app.use('/api/bronze/*', rateLimitDO({
window: 3600, max: 100, do: 'RATE_LIMITER'
}));
// 🥈 Silver: 1000/hour
app.use('/api/silver/*', rateLimitDO({
window: 3600, max: 1000, do: 'RATE_LIMITER'
}));
// 🥇 Gold: 10000/hour
app.use('/api/gold/*', rateLimitDO({
window: 3600, max: 10000, do: 'RATE_LIMITER'
}));📊 Enhanced Headers
DO middleware provides more detailed headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1699123456
X-RateLimit-Reset-After: 45
X-RateLimit-Policy: sliding-window
Retry-After: 45🔄 So sánh Response Format
KV Response (429 Error)
{
"error": "Too many requests",
"limit": 100,
"remaining": 0,
"resetAt": "2024-11-14T21:30:00.000Z",
"retryAfter": 45
}DO Response (429 Error)
{
"error": "Too many requests",
"limit": 100,
"remaining": 0,
"resetAt": "2024-11-14T21:30:00.000Z",
"retryAfter": 45,
"policy": "sliding-window"
}🛡️ Production Best Practices
✅ Error Handling
Rate limiter is designed to fail-open:
- ❌ KV timeout → Allow request + Warning
- ❌ DO error → Allow request + Log error
- ❌ Invalid config → Throw at startup
⚡ Performance Features
- Async operations: Non-blocking main request
- Timeout protection: 5s timeout for all operations
- Memory management: Auto cleanup, size limits
- Race condition protection: Thread-safe operations
📊 Monitoring
// Enable performance logging
import { logPerformance } from 'cf-rate-limit';
// Logs will appear when operation > 1s
// "Slow rate limit operation: { operation, duration, ...metadata }"🚀 Migration Guide
From v1.x → v2.x
// ❌ Old v1.x
rateLimit({
duration: 60, // → window
limit: 100, // → max
kvBinding: 'MY_KV', // → kv
keyGenerator: fn, // → keyMaker
responseMessage: msg, // → message
})
// ✅ New v2.x
rateLimit({
window: 60, // Renamed
max: 100, // Renamed
kv: 'MY_KV', // Simplified
keyMaker: fn, // Shorter name
message: msg, // Shorter name
})🔗 TypeScript Support
Full TypeScript support with auto-completion:
import type {
KVRateLimit,
DORateLimit,
RateLimitData,
RateLimitStatus,
KeyMaker
} from 'cf-rate-limit';
// Custom key maker with type safety
const customKeyMaker: KeyMaker = (c) => {
return `user:${c.req.header('x-user-id') || 'anonymous'}`;
};🤝 Contributing
- Fork repository
- Create feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open Pull Request
📄 License
MIT © thieenlabs
⭐ Bonus: Quick Comparison
| Scenario | Recommended Method | Reason |
|----------|-------------------|--------|
| 🌐 Public API | KV Storage | Cost-effective, good enough |
| 💰 Payment API | Durable Objects | Need exact precision |
| 📱 Mobile App | KV Storage | Fast response, lower latency |
| 🏢 Enterprise | Durable Objects | Accurate billing, compliance |
| 🚀 Startup | KV Storage | Simple setup, lower costs |
| 🏦 Banking | Durable Objects | Security, precision required |
Choose KV for 80% of use cases. Choose DO when absolute precision is required! 🎯
