Skip to content

Commit 1726fca

Browse files
committed
docs: add sliding window rate limiter section to middleware documentation
1 parent 11674fe commit 1726fca

File tree

1 file changed

+115
-3
lines changed

1 file changed

+115
-3
lines changed

lib/middleware/README.md

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ router.use(
173173
},
174174
}),
175175
)
176-
````
176+
```
177177
178178
**TypeScript Usage:**
179179
@@ -590,15 +590,127 @@ class CustomStore implements RateLimitStore {
590590
}
591591
```
592592
593-
````
593+
#### Sliding Window Rate Limiter
594+
595+
For more precise rate limiting, use the sliding window implementation that **prevents burst traffic** at any point in time:
596+
597+
```javascript
598+
const {createSlidingWindowRateLimit} = require('0http-bun/lib/middleware')
599+
600+
// Basic sliding window rate limiter
601+
router.use(
602+
createSlidingWindowRateLimit({
603+
windowMs: 60 * 1000, // 1 minute sliding window
604+
max: 10, // Max 10 requests per minute
605+
keyGenerator: (req) => req.headers.get('x-forwarded-for') || 'default',
606+
}),
607+
)
608+
```
609+
610+
**TypeScript Usage:**
611+
612+
```typescript
613+
import {createSlidingWindowRateLimit} from '0http-bun/lib/middleware'
614+
import type {RateLimitOptions} from '0http-bun/lib/middleware'
615+
616+
const slidingOptions: RateLimitOptions = {
617+
windowMs: 60 * 1000, // 1 minute
618+
max: 10, // 10 requests max
619+
keyGenerator: (req) => req.user?.id || req.headers.get('x-forwarded-for'),
620+
handler: (req, hits, max, resetTime) => {
621+
return Response.json(
622+
{
623+
error: 'Rate limit exceeded',
624+
retryAfter: Math.ceil((resetTime.getTime() - Date.now()) / 1000),
625+
limit: max,
626+
used: hits,
627+
},
628+
{status: 429},
629+
)
630+
},
631+
}
632+
633+
router.use(createSlidingWindowRateLimit(slidingOptions))
634+
```
635+
636+
**How Sliding Window Differs from Fixed Window:**
637+
638+
The sliding window approach provides **more accurate and fair rate limiting** by tracking individual request timestamps:
639+
640+
- **Fixed Window**: Divides time into discrete chunks (e.g., 09:00:00-09:00:59, 09:01:00-09:01:59)
641+
- ⚠️ **Problem**: Allows burst traffic at window boundaries (20 requests in 2 seconds)
642+
- **Sliding Window**: Uses a continuous, moving time window from current moment
643+
- ✅ **Advantage**: Prevents bursts at any point in time (true rate limiting)
644+
645+
**Use Cases for Sliding Window:**
646+
647+
```javascript
648+
// Financial API - Zero tolerance for payment bursts
649+
router.use(
650+
'/api/payments/*',
651+
createSlidingWindowRateLimit({
652+
windowMs: 60 * 1000, // 1 minute
653+
max: 3, // Only 3 payment attempts per minute
654+
keyGenerator: (req) => req.user.accountId,
655+
}),
656+
)
657+
658+
// User Registration - Prevent automated signups
659+
router.use(
660+
'/api/register',
661+
createSlidingWindowRateLimit({
662+
windowMs: 3600 * 1000, // 1 hour
663+
max: 3, // 3 accounts per IP per hour
664+
keyGenerator: (req) => req.headers.get('x-forwarded-for'),
665+
}),
666+
)
667+
668+
// File Upload - Prevent abuse
669+
router.use(
670+
'/api/upload',
671+
createSlidingWindowRateLimit({
672+
windowMs: 300 * 1000, // 5 minutes
673+
max: 10, // 10 uploads per 5 minutes
674+
keyGenerator: (req) => req.user.id,
675+
}),
676+
)
677+
```
678+
679+
**Performance Considerations:**
680+
681+
- **Memory Usage**: Higher than fixed window (stores timestamp arrays)
682+
- **Time Complexity**: O(n) per request where n = requests in window
683+
- **Best For**: Critical APIs, financial transactions, user-facing features
684+
- **Use Fixed Window For**: High-volume APIs where approximate limiting is acceptable
685+
686+
**Advanced Configuration:**
687+
688+
```typescript
689+
// Tiered rate limiting based on user level
690+
const createTieredRateLimit = (req) => {
691+
const userTier = req.user?.tier || 'free'
692+
const configs = {
693+
free: {windowMs: 60 * 1000, max: 10},
694+
premium: {windowMs: 60 * 1000, max: 100},
695+
enterprise: {windowMs: 60 * 1000, max: 1000},
696+
}
697+
return createSlidingWindowRateLimit(configs[userTier])
698+
}
699+
```
594700
595701
**Rate Limit Headers:**
596702
703+
Both rate limiters send the following headers when `standardHeaders: true`:
704+
597705
- `X-RateLimit-Limit` - Request limit
598706
- `X-RateLimit-Remaining` - Remaining requests
599707
- `X-RateLimit-Reset` - Reset time (Unix timestamp)
600708
- `X-RateLimit-Used` - Used requests
601709
710+
**Error Handling:**
711+
712+
Rate limiting middleware allows errors to bubble up as proper HTTP 500 responses. If your `keyGenerator` function or custom `store.increment()` method throws an error, it will not be caught and masked - the error will propagate up the middleware chain for proper error handling.
713+
602714
## Creating Custom Middleware
603715
604716
### Basic Middleware
@@ -619,7 +731,7 @@ const customMiddleware = (req: ZeroRequest, next: StepFunction) => {
619731
}
620732

621733
router.use(customMiddleware)
622-
````
734+
```
623735
624736
### Async Middleware
625737

0 commit comments

Comments
 (0)