Cloudflare
Rate limiting on Cloudflare Workers with hono-rate-limiter
If you're deploying to Cloudflare Workers, we recommend using Cloudflare's native Rate Limiting API. It's built into the Workers runtime, requires no external dependencies, and provides excellent performance with minimal latency.
Rate Limiting API
Cloudflare's Rate Limiting API is the simplest and most efficient way to add rate limiting to your Cloudflare Workers. It uses Cloudflare's built-in infrastructure at the edge.
Configuration
First, add a rate limit binding to your Wrangler configuration:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"main": "src/index.ts",
"ratelimits": [
{
"name": "MY_RATE_LIMITER",
"namespace_id": "1001",
"simple": {
"limit": 100,
"period": 60
}
}
]
}main = "src/index.ts"
[[ratelimits]]
name = "MY_RATE_LIMITER"
# An identifier you define, unique to your Cloudflare account (must be an integer)
namespace_id = "1001"
# Limit: number of requests allowed within the period
# Period: duration in seconds (must be 10 or 60)
simple = { limit = 100, period = 60 }Usage
Use the rateLimiter with the binding option:
import { Hono } from "hono";
import { rateLimiter } from "hono-rate-limiter";
type Env = {
MY_RATE_LIMITER: RateLimit;
};
const app = new Hono<{ Bindings: Env }>();
app.use(
rateLimiter<{ Bindings: Env }>({
binding: (c) => c.env.MY_RATE_LIMITER,
keyGenerator: (c) => c.req.header("cf-connecting-ip") ?? "",
})
);
app.get("/", (c) => c.text("Hello!"));
export default app;How It Works
When you use the binding option, hono-rate-limiter uses Cloudflare's native rate limiting:
- Rate limits are configured in your Wrangler config, not in code
- No
windowMsorlimitoptions needed (configured in binding) - The rate limit check is performed at the edge with minimal latency
- Rate limits are local to each Cloudflare location
Configuration Options
rateLimiter({
binding: (c) => c.env.MY_RATE_LIMITER, // Required: Rate limit binding
keyGenerator: (c) => string, // Required: Client identifier
message: "Rate limit exceeded", // Optional: Response message
statusCode: 429, // Optional: HTTP status
handler: (c, next, options) => {}, // Optional: Custom handler
skip: (c) => boolean, // Optional: Skip logic
});Multiple Rate Limits
You can define different rate limits for different types of users:
{
"ratelimits": [
{
"name": "FREE_USER_RATE_LIMITER",
"namespace_id": "1001",
"simple": { "limit": 100, "period": 60 }
},
{
"name": "PAID_USER_RATE_LIMITER",
"namespace_id": "1002",
"simple": { "limit": 1000, "period": 60 }
}
]
}# Free user rate limiting
[[ratelimits]]
name = "FREE_USER_RATE_LIMITER"
namespace_id = "1001"
simple = { limit = 100, period = 60 }
# Paid user rate limiting
[[ratelimits]]
name = "PAID_USER_RATE_LIMITER"
namespace_id = "1002"
simple = { limit = 1000, period = 60 }type Env = {
FREE_USER_RATE_LIMITER: RateLimit;
PAID_USER_RATE_LIMITER: RateLimit;
};
app.use(
rateLimiter<{ Bindings: Env }>({
binding: (c) => {
const isPaidUser = c.get("isPaidUser");
return isPaidUser ? c.env.PAID_USER_RATE_LIMITER : c.env.FREE_USER_RATE_LIMITER;
},
keyGenerator: (c) => c.get("userId") ?? "",
})
);Complete Example
import { Hono } from "hono";
import { rateLimiter } from "hono-rate-limiter";
type Env = {
API_RATE_LIMITER: RateLimit;
};
const app = new Hono<{ Bindings: Env }>();
// Apply rate limiting to API routes
app.use(
"/api/*",
rateLimiter<{ Bindings: Env }>({
binding: (c) => c.env.API_RATE_LIMITER,
keyGenerator: (c) => {
// Use API key if available, fallback to IP
return c.req.header("x-api-key") ?? c.req.header("cf-connecting-ip") ?? "";
},
message: { error: "Rate limit exceeded", retryAfter: "60s" },
})
);
app.get("/api/data", (c) => {
return c.json({ message: "Hello from the API!" });
});
export default app;With wrangler.toml:
name = "my-rate-limited-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[ratelimits]]
name = "API_RATE_LIMITER"
namespace_id = "1001"
simple = { limit = 100, period = 60 }Workers KV Store (Legacy)
Legacy
This store is available via the @hono-rate-limiter/cloudflare package. We recommend using the Rate Limiting API instead for new projects.
Use Cloudflare KV for globally distributed rate limiting. KV provides eventual consistency, which is suitable for most rate limiting use cases.
Installation
npm install @hono-rate-limiter/cloudflareConfiguration
Add a KV namespace binding to your wrangler.toml:
[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "your-namespace-id"Usage
import { Hono } from "hono";
import { rateLimiter } from "hono-rate-limiter";
import { WorkersKVStore } from "@hono-rate-limiter/cloudflare";
type Env = {
RATE_LIMIT_KV: KVNamespace;
};
const app = new Hono<{ Bindings: Env }>();
app.use(async (c, next) => {
const limiter = rateLimiter({
windowMs: 60_000,
limit: 100,
keyGenerator: (c) => c.req.header("cf-connecting-ip") ?? "",
store: new WorkersKVStore({
namespace: c.env.RATE_LIMIT_KV,
}),
});
return limiter(c, next);
});
export default app;Configuration Options
new WorkersKVStore({
namespace: kvNamespace, // Required: KV namespace binding
prefix: "hrl:", // Optional: Key prefix (default: "hrl:")
});KV Expiration Limitation
Cloudflare KV has a minimum expiration of 60 seconds. The store automatically adjusts expiration times to meet this requirement. This doesn't affect rate limiting behavior, which is controlled by resetTime.
Limitations
- Eventually consistent: May briefly allow over-limit requests
- 60-second minimum expiration: Cannot use shorter windows
Durable Objects Store (Legacy)
Legacy
This store is available via the @hono-rate-limiter/cloudflare package. We recommend using the Rate Limiting API instead for new projects.
Durable Objects provide strong consistency and are ideal when you need precise rate limiting with transactional guarantees.
Installation
npm install @hono-rate-limiter/cloudflareConfiguration
First, create the Durable Object class by exporting it from your worker:
// Export the Durable Object class
export { DurableObjectRateLimiter } from "@hono-rate-limiter/cloudflare";Add the Durable Object binding to your wrangler.toml:
[durable_objects]
bindings = [
{ name = "RATE_LIMITER", class_name = "DurableObjectRateLimiter" }
]
[[migrations]]
tag = "v1"
new_classes = ["DurableObjectRateLimiter"]Usage
import { Hono } from "hono";
import { rateLimiter } from "hono-rate-limiter";
import {
DurableObjectStore,
DurableObjectRateLimiter,
} from "@hono-rate-limiter/cloudflare";
// Re-export for Cloudflare to find the DO class
export { DurableObjectRateLimiter };
type Env = {
RATE_LIMITER: DurableObjectNamespace<DurableObjectRateLimiter>;
};
const app = new Hono<{ Bindings: Env }>();
app.use(async (c, next) => {
const limiter = rateLimiter({
windowMs: 60_000,
limit: 100,
keyGenerator: (c) => c.req.header("cf-connecting-ip") ?? "",
store: new DurableObjectStore({
namespace: c.env.RATE_LIMITER,
}),
});
return limiter(c, next);
});
export default app;Configuration Options
new DurableObjectStore({
namespace: doNamespace, // Required: DO namespace binding
prefix: "hrl:", // Optional: Key prefix (default: "hrl:")
});How It Works
The DurableObjectRateLimiter class extends Cloudflare's DurableObject:
- Each unique key maps to a Durable Object instance
- Hit counts are stored in the DO's transactional storage
- An alarm is set to reset the count when the window expires
- Operations are strongly consistent within each DO
Limitations
- Higher latency: Single point of coordination per key
- Complex setup: Requires migrations and class exports
- Higher cost: More expensive for high-traffic scenarios