// the find
jhurliman/node-rate-limiter
A generic rate limiter for node.js. Useful for API clients, web crawling, or other tasks that need to be throttled
A zero-dependency rate limiter for Node.js and browsers with two primitives: RateLimiter, which caps tokens per interval with continuous refill underneath, and TokenBucket, which separates burst size from refill rate and can chain through parent buckets. It suits API clients, message throttling, and byte budgeting inside a single process.
- Waiting callers are served FIFO from one timer per instance, so thousands of pending removals don't each spawn their own timeout. The test suite checks concurrent accounting and FIFO backlogs against deterministic clocks instead of real sleeps.
- A removal through a parent/child bucket charges the child and every finite ancestor together, and a failed attempt charges none. Hand-rolled limiters often get this wrong, and it is the main reason to use TokenBucket over chaining two RateLimiters, which the README explicitly warns can let work pile up behind the second limiter.
- The docs are candid about what the limiter does not do. It says plainly that RateLimiter is not a rolling window, does not cap in-flight concurrency, and does not share state across processes. Most limiter READMEs skip that.
- No runtime dependencies, with CJS and ESM builds and bundled TypeScript declarations. The browser support is real, not just claimed.
- The zero-value conventions are a trap. bucketSize: 0 means unlimited and bypasses parents, and tokensPerInterval: 0 refills a finite bucket to capacity on every attempt. A config value that accidentally becomes zero removes the limit instead of blocking traffic, and the README has to explain this in three separate places.
- State lives in the process. Run the same code across a cluster or several workers and each one gets its own full budget, which is the usual way a rate limit fails in production. The docs say so, but the package name and the example budgets don't hint at it.
- The FIFO queue has head-of-line blocking by design. A large request at the front delays every smaller request behind it. tryRemoveTokens and fireImmediately skip the queue and can take capacity that waiting callers were promised, so mixing the two styles in one instance is easy to get wrong.
- Balances are JavaScript floats, so fractional accounting carries rounding error, and the 4.0 changelog says not to test exact equality on them. The 4.0 accounting fix also changed observable waits for code that relied on the old overshoot, so upgrading from 3.x is not a drop-in change for anyone whose timing was tuned to it.