Per-User Rate Limiting
Key the rate limiter by authenticated user instead of IP address.
The default @nextrush/rate-limit key is the client IP — fine for anonymous traffic, but it
under-protects you when many users share one IP (corporate NAT, mobile carrier) and
over-throttles them together. If requests are already authenticated, key the limiter by user ID
instead using keyGenerator.
Solution
import { createApp, listen, errorHandler } from 'nextrush';
import { rateLimit } from '@nextrush/rate-limit';
import { requireAuth } from './middleware/require-auth'; // see the JWT recipe
const app = createApp();
app.use(errorHandler());
const perUserLimit = rateLimit({
max: 100,
window: '1m',
// Falls back to the client IP for requests that somehow reach this
// middleware unauthenticated — keyGenerator must always return a key.
keyGenerator: (ctx) => {
const user = ctx.state.user as { id: string } | undefined;
return user ? `user:${user.id}` : `ip:${ctx.ip}`;
},
});
app.get('/api/reports', requireAuth, perUserLimit, (ctx) => {
ctx.json({ reports: [] });
});
listen(app, 8080);keyGenerator is a real RateLimitOptions field (packages/middleware/rate-limit/src/types.ts)
— it receives the full Context and returns the string key the limiter tracks state under, so
each authenticated user gets an independent counter instead of sharing their IP's bucket.
requireAuth must run before perUserLimit so ctx.state.user is populated by the time the
key generator reads it. See Middleware for composition order.
Tiered limits by plan
If different user tiers (free/pro) need different limits rather than only different keys, use
tieredRateLimit() with a tierResolver instead of a single rateLimit() call — see the
rate-limit reference for TieredRateLimitOptions.
Try It
# Two different users, same IP — independent limits
curl http://localhost:8080/api/reports -H "Authorization: Bearer $TOKEN_USER_A"
curl http://localhost:8080/api/reports -H "Authorization: Bearer $TOKEN_USER_B"Expected result: both succeed independently; each response carries RateLimit-Remaining
scoped to its own user:<id> key, not a shared IP-based counter.
Related
- JWT Authentication (Functional) — populate
ctx.state.userbefore this recipe - Middleware — execution order and
ctx.next() - Rate Limiting reference — full
RateLimitOptions