Fastify
Fastify
Rate limiting plugin for Fastify 4 and 5, powered by any RateLock engine.
- Engine-agnostic: bring a
@ratelock/local,@ratelock/redisor@ratelock/postgreslimiter - Both majors supported: wrapped in
fastify-plugin, the hook applies across encapsulation scopes - Lazy initialization: pass a factory and the engine starts on the first request after boot
- Dual-family headers:
RateLimit-*(RFC 9331) andX-RateLimit-*, configurable
Installation
pnpm add @ratelock/fastify @ratelock/redisQuick start
import Fastify from 'fastify'
import { rateLimit } from '@ratelock/fastify'
import { fixedWindow } from '@ratelock/redis'
const app = Fastify({ trustProxy: true })
await app.register(rateLimit, {
// Lazy factory: nothing initializes until the first request after boot
limiter: () => fixedWindow({ url: process.env.REDIS_URL!, limit: 100, windowMs: 60_000 }),
limit: 100,
keyGenerator: request => request.ip,
})Already have an instance? Pass it directly:
const limiter = await fixedWindow({ limit: 100, windowMs: 60_000 })
await app.register(rateLimit, { limiter })The plugin adds an onRequest hook, the earliest point of the Fastify lifecycle. Global hooks run even on unmatched paths (404s included), so a lazy factory initializes on the first request of any kind.
Options
| Option | Type | Default | Description |
|---|---|---|---|
limiter | Limiter | (() => Limiter | Promise<Limiter>) | required | Any RateLock limiter. A factory is memoized and invoked once, on the first request. |
keyGenerator | (request) => string | Promise<string> | request.ip | Identifier the request is counted against; falls back to a shared 'anonymous' bucket when unknown. |
headers | 'both' | 'rfc' | 'legacy' | false | 'both' | Header families attached to responses. |
limit | number | (none) | Quota, used only to emit the *Limit headers. |
denyStatusCode | number | 429 | Status returned on exhaustion. |
message | string | 'Too Many Requests' | JSON body of denial responses. |
Response headers
| Family | Headers |
|---|---|
| RFC 9331 | RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset |
| Legacy | X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset |
| Denial only | Retry-After |
RateLimit-Reset is always in seconds. Token bucket results project floor(tokens) onto *Remaining and the refill time onto Reset.
Identifiers behind proxies
The default identifier reads request.ip, which honors your server's trustProxy option. Behind a load balancer without trust configuration, all clients share one bucket. Set trustProxy correctly or provide a keyGenerator. Never read raw x-forwarded-for yourself: attackers forge it per-request to bypass limits entirely.
How is this guide?
Last updated on