Integrations
Hono
Hono
Rate limiting middleware for Hono, powered by any RateLock engine.
- Engine-agnostic: bring a
@ratelock/local,@ratelock/redisor@ratelock/postgreslimiter - Lazy initialization: pass a factory and the engine starts on the first matched request (cold-start friendly for edge/serverless)
- Dual-family headers:
RateLimit-*(RFC 9331) andX-RateLimit-*, configurable - Zero runtime dependency besides Hono itself
Installation
pnpm add @ratelock/hono @ratelock/redisQuick start
import { Hono } from 'hono'
import { rateLimit } from '@ratelock/hono'
import { fixedWindow } from '@ratelock/redis'
const app = new Hono()
app.use(
'/api/*',
rateLimit({
// Lazy factory: nothing initializes until the first request hits /api/*
limiter: () => fixedWindow({ url: process.env.REDIS_URL!, limit: 100, windowMs: 60_000 }),
limit: 100,
keyGenerator: c => c.req.header('x-user-id') ?? 'anon',
})
)
app.get('/api/things', c => c.json({ things: [] }))Already have an instance? Pass it directly:
const limiter = await fixedWindow({ limit: 100, windowMs: 60_000 })
app.use('/api/*', rateLimit({ limiter }))Options
| Option | Type | Default | Description |
|---|---|---|---|
limiter | Limiter | (() => Limiter | Promise<Limiter>) | required | Any RateLock limiter. A factory is memoized and invoked once, on the first matched request. |
keyGenerator | (c) => string | Promise<string> | remote address | Identifier the request is counted against; falls back to a shared 'anonymous' bucket when no address is available. |
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 is the runtime-provided remote address (Node socket, Bun requestIP, Deno remoteAddr). Behind a load balancer that address is your proxy's, so all clients would share one bucket. Configure your runtime's trust settings or provide a keyGenerator. Never read raw x-forwarded-for from untrusted clients: attackers can forge it per-request to bypass limits entirely.
How is this guide?
Last updated on