Redis Store
Using Redis as a data store for hono-rate-limiter
The RedisStore uses Redis to store rate limit data, enabling consistent rate limiting across multiple servers and processes. It's ideal for production deployments where you need shared state.
Installation
The Redis store requires a Redis client. You can use @upstash/redis for serverless environments or any Redis client that implements the required interface.
npm install hono-rate-limiter @upstash/redisBasic Usage
import { Hono } from "hono";
import { rateLimiter, RedisStore } from "hono-rate-limiter";
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const app = new Hono();
app.use(
rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
limit: 100,
keyGenerator: (c) => c.req.header("x-forwarded-for") ?? "",
store: new RedisStore({ client: redis }),
})
);
export default app;Configuration Options
new RedisStore({
client: redis, // Required: Redis client instance
prefix: "hrl:", // Optional: Key prefix (default: "hrl:")
resetExpiryOnChange: false, // Optional: Reset TTL on each hit
});client
The Redis client instance. Must implement the following interface:
type RedisClient = {
scriptLoad: (script: string) => Promise<string>;
evalsha: <TArgs extends unknown[], TData = unknown>(
sha1: string,
keys: string[],
args: TArgs,
) => Promise<TData>;
decr: (key: string) => Promise<number>;
del: (key: string) => Promise<number>;
};prefix
A string prepended to all Redis keys. Useful for namespacing when sharing a Redis instance.
new RedisStore({
client: redis,
prefix: "api-rate-limit:", // Keys will be like "api-rate-limit:user-123"
});resetExpiryOnChange
When true, the key's TTL is reset each time the hit count changes. When false (default), the TTL is only set when the key is first created.
new RedisStore({
client: redis,
resetExpiryOnChange: true, // Sliding window behavior
});Using with Upstash Redis
Upstash provides a serverless Redis service that works well with edge deployments.
import { Redis } from "@upstash/redis";
import { RedisStore } from "hono-rate-limiter";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
const store = new RedisStore({ client: redis });Cloudflare Workers
For Cloudflare Workers, use the Cloudflare-specific import:
import { Redis } from "@upstash/redis/cloudflare";
import { rateLimiter, RedisStore } from "hono-rate-limiter";
export default {
async fetch(request: Request, env: Env) {
const redis = new Redis({
url: env.UPSTASH_REDIS_REST_URL,
token: env.UPSTASH_REDIS_REST_TOKEN,
});
const app = new Hono();
app.use(
rateLimiter({
windowMs: 60_000,
limit: 100,
keyGenerator: (c) => c.req.header("cf-connecting-ip") ?? "",
store: new RedisStore({ client: redis }),
})
);
return app.fetch(request, env);
},
};Using with Vercel KV
Vercel KV is a Redis-compatible key-value store built on Upstash.
import { kv } from "@vercel/kv";
import { RedisStore } from "hono-rate-limiter";
const store = new RedisStore({ client: kv });
app.use(
rateLimiter({
windowMs: 60_000,
limit: 100,
keyGenerator: (c) => c.req.header("x-forwarded-for") ?? "",
store,
})
);How It Works
The Redis store uses Lua scripts for atomic operations, preventing race conditions when multiple requests arrive simultaneously.
Increment Script
When a request comes in, the store:
- Increments the key's value atomically
- Sets the expiration if it's a new key or
resetExpiryOnChangeis true - Returns both the hit count and time-to-expire
local totalHits = redis.call("INCR", KEYS[1])
local timeToExpire = redis.call("PTTL", KEYS[1])
if timeToExpire <= 0 or ARGV[1] == "1" then
redis.call("PEXPIRE", KEYS[1], tonumber(ARGV[2]))
timeToExpire = tonumber(ARGV[2])
end
return { totalHits, timeToExpire }Key Structure
Keys are stored as: {prefix}{clientKey}
For example, with the default prefix and an IP-based key:
- Key:
hrl:192.168.1.1 - Value: Integer (hit count)
- TTL: Automatically set to
windowMs
Complete Example
import { Hono } from "hono";
import { rateLimiter, RedisStore } from "hono-rate-limiter";
import { Redis } from "@upstash/redis";
const app = new Hono();
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// Rate limit by API key
app.use(
"/api/*",
rateLimiter({
windowMs: 60 * 1000, // 1 minute
limit: 60, // 60 requests per minute
keyGenerator: (c) => {
const apiKey = c.req.header("x-api-key");
if (!apiKey) throw new Error("API key required");
return apiKey;
},
store: new RedisStore({
client: redis,
prefix: "api-limit:",
}),
handler: (c) => {
return c.json(
{
error: "Rate limit exceeded",
retryAfter: c.res.headers.get("Retry-After"),
},
429
);
},
})
);
export default app;