One Durable Object Per User, Not One For Everyone
The obvious way to build a rate limiter on Durable Objects is a counter. One object, a map of keys to counts, every request checks in. It works in development and it’s the wrong shape.
A Durable Object is a single instance living in a single location. Route every request through one and you’ve built a global bottleneck: requests are serialized, and a user in Sydney pays a round trip to wherever the object happens to live before your Worker does any actual work.
Shard by the thing you’re limiting instead:
const id = env.LIMITER.idFromName(apiKey);const allowed = await env.LIMITER.get(id).take();
if (!allowed) return new Response('Too many requests', { status: 429 });Now each key has its own object, placed near where it’s first used. Sydney traffic limits in Sydney. Throughput scales with the number of keys because nothing is shared.
The limiter itself
Single-threaded execution means no locking and no compare-and-swap. Refill lazily from the clock, so there’s no alarm and no timer to manage:
export class RateLimiter extends DurableObject { private tokens = CAPACITY; private updatedAt = Date.now();
async take(): Promise<boolean> { const now = Date.now(); const refill = ((now - this.updatedAt) / 1000) * REFILL_PER_SECOND;
this.tokens = Math.min(CAPACITY, this.tokens + refill); this.updatedAt = now;
if (this.tokens < 1) return false; this.tokens -= 1; return true; }}State lives in memory, not storage. An idle object eventually gets evicted and the bucket resets, which means the limiter fails open. For protecting a backend that’s the right default. For quota you bill against, persist it and accept the write cost.
Check the cheaper option first
Every check is a Durable Object request plus its duration, billed. That’s reasonable for per-user limits with real business logic attached.
If all you need is a fixed window per IP in front of a public endpoint, Workers has a built-in rate limiting binding. No object, no code, no bill. Reach for Durable Objects when you need the state to be yours.
