Cap how much traffic a client can send in a given window, so one caller can't exhaust capacity meant for everyone.
Without a cap, one misbehaving script — or one tenant with an unusually bursty workload — can consume enough of a shared API's capacity that other, well-behaved clients start seeing slow responses or errors, even though they did nothing wrong.
Each client gets a bucket that refills with tokens at a fixed rate, up to a cap. Every request consumes one token; a request with no tokens available is rejected (or delayed) until more refill. This allows small bursts while still capping the sustained rate.
Tokens refill continuously based on elapsed time; a request is only allowed if at least one token is currently available.
class TokenBucket {
#capacity; #tokens; #refillPerMs; #lastRefill;
constructor(capacity, refillPerSecond) {
this.#capacity = capacity;
this.#tokens = capacity;
this.#refillPerMs = refillPerSecond / 1000;
this.#lastRefill = Date.now();
}
#refill() {
const now = Date.now();
const elapsed = now - this.#lastRefill;
this.#tokens = Math.min(this.#capacity, this.#tokens + elapsed * this.#refillPerMs);
this.#lastRefill = now;
}
tryConsume() {
this.#refill();
if (this.#tokens >= 1) {
this.#tokens -= 1;
return true;
}
return false;
}
}
const bucket = new TokenBucket(5, 1); // 5 burst capacity, refills 1/sec
for (let i = 1; i <= 7; i++) {
console.log(`request ${i}: ${bucket.tryConsume() ? "200 OK" : "429 rate limited"}`);
}