Ratelock

Integrations

Fastify

Fastify

Rate limiting plugin for Fastify 4 and 5, powered by any RateLock engine.

  • Engine-agnostic: bring a @ratelock/local, @ratelock/redis or @ratelock/postgres limiter
  • 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) and X-RateLimit-*, configurable

Installation

pnpm add @ratelock/fastify @ratelock/redis

Quick 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

OptionTypeDefaultDescription
limiterLimiter | (() => Limiter | Promise<Limiter>)requiredAny RateLock limiter. A factory is memoized and invoked once, on the first request.
keyGenerator(request) => string | Promise<string>request.ipIdentifier 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.
limitnumber(none)Quota, used only to emit the *Limit headers.
denyStatusCodenumber429Status returned on exhaustion.
messagestring'Too Many Requests'JSON body of denial responses.

Response headers

FamilyHeaders
RFC 9331RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
LegacyX-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Denial onlyRetry-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

On this page