Library / SDK
jhurliman/node-rate-limiter avatar
jhurliman/node-rate-limiter

limiter for Node.js pacing and token budgets

A generic rate limiter for node.js. Useful for API clients, web crawling, or other tasks that need to be throttled

1,568 stars133 forksTypeScriptMIT

At a glance

What is it?
The limiter package controls when work starts in Node.js and browsers through interval pacing, token buckets, and hierarchical budgets. It fits API clients and crawlers, but it does not enforce rolling-window or concurrency limits.
Who is it for?
Adopt limiter for request pacing, byte budgets, and token-bucket constraints when one shared Node.js process owns the resource. Do not use it as the sole control for rolling windows or maximum concurrent work.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 42 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

limiter's place in Node.js request scheduling

limiter solves admission scheduling rather than task execution. It controls when an API request, crawl step, outbound message, or byte transfer is allowed to start. The application defines one token as one request, one message, one byte, or a fractional cost, then asks the limiter to remove that amount. A call to `removeTokens()` waits until capacity exists, consumes the tokens when possible, and resolves with the remaining bucket balance. The calling code begins its actual work after that promise resolves.

The package supports Node.js and browsers and has no runtime dependencies. Its published package metadata includes CommonJS entry points, ES module entry points, and TypeScript declarations. This makes it usable from a server application, a crawler, or browser-side code without requiring an external token-bucket service.

There are two scheduling models. `RateLimiter` is intended for a configured count per interval and starts with a full bucket. `TokenBucket` separates burst capacity from refill rate and starts empty. That difference matters when choosing between a straightforward per-second request budget and a system that should permit a large burst after an idle period.

Inside RateLimiter's interval and refill mechanism

`RateLimiter` combines a continuously refilling token bucket with an interval counter. The counter starts when the limiter is constructed and resets on the first removal attempt at or after the previous interval expires. This produces an interval allowance while the underlying bucket replenishes over time.

The behavior is not a sliding-window counter. Traffic near an interval boundary can exceed the configured count when measured over a sliding window. For example, the algorithm should not be interpreted as enforcing exactly ten events in every moving time span. It enforces its documented interval mechanism instead. A developer who needs strict rolling-window behavior must select a different algorithm.

Waiting calls are handled in FIFO order on each limiter instance, with one active timer per instance even when many callers are pending. `tryRemoveTokens()` offers a different path: it atomically checks capacity and consumes tokens only when the request can proceed. Immediate calls and calls configured with `fireImmediately: true` do not join the FIFO waiting queue, so they may consume capacity before earlier calls that are already waiting. Applications using mixed admission styles need to account for that ordering difference.

Installing limiter and pacing the first three messages

The README directs users to install the published `limiter` package from npm. This first example creates one limiter per resource and shares it across all three message operations. Each operation removes one token, so the first message starts immediately and later messages start at least 250 milliseconds apart.

Save the file as `example.mjs` and run `node example.mjs` as documented:

sh
npm install limiter
js
import { RateLimiter } from "limiter";

const limiter = new RateLimiter({ tokensPerInterval: 1, interval: 250 });

await Promise.all(["first", "second", "third"].map(async (message) => {
  await limiter.removeTokens(1);
  console.log(message);
}));

Reuse the same limiter instance whenever work should share a budget. Creating a new limiter for each operation would give each operation an independent interval counter. CommonJS applications can import both classes with `const { RateLimiter, TokenBucket } = require("limiter")`.

HTTP 429 handling with tryRemoveTokens

An HTTP server may prefer rejection over making requests wait. The `tryRemoveTokens()` method performs the capacity check and consumption as one operation, returning a boolean rather than a promise. The README's complete example shares a ten-request-per-second limiter across incoming HTTP requests. Port `3000` is the documented listening port.

js
import { createServer } from "node:http";
import { RateLimiter } from "limiter";

const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" });

createServer((request, response) => {
  if (!limiter.tryRemoveTokens(1)) {
    const seconds = Math.max(1, Math.ceil(limiter.getWaitTime(1) / 1000));
    response.writeHead(429, { "Retry-After": String(seconds) });
    response.end("Too many requests\n");
    return;
  }
  response.end("Accepted\n");
}).listen(3000);

`getWaitTime(count)` returns an estimated delay in milliseconds without consuming tokens. It is not a reservation, and it ignores queued requests, so another call can change capacity before the estimate is used. The example converts the estimate to seconds and returns it in `Retry-After` when immediate admission fails.

For the Promise API, `fireImmediately: true` changes denial into a resolved value of `-1`. Invalid input still rejects the promise. This distinction matters because callers must distinguish an expected capacity denial from an invalid request when handling errors.

Byte budgets and hierarchical TokenBucket limits

`TokenBucket` is the better fit when burst size and sustained refill rate need independent settings. A standalone bucket begins empty. The documented example allows a burst of 150 KiB after sufficient idle time and replenishes 50 KiB each second. Chunks are charged according to `chunk.byteLength`.

js
import { TokenBucket } from "limiter";

const bucket = new TokenBucket({
  bucketSize: 150 * 1024,
  tokensPerInterval: 50 * 1024,
  interval: "second"
});

const chunks = [new Uint8Array(1024), new Uint8Array(2048)];
for (const chunk of chunks) {
  await bucket.removeTokens(chunk.byteLength);
  console.log(`Ready to send ${chunk.byteLength} bytes`);
}

A chunk larger than `bucketSize` cannot ever fit, so the README instructs callers to split it into smaller chunks. Parent buckets let independent child budgets share one parent constraint. A successful child removal charges the child and every finite ancestor. If the combined charge cannot succeed, none of the buckets are charged.

This hierarchy is the documented choice for atomic burst and sustained-rate budgets. Awaiting two independent limiters in sequence does not provide the same guarantee. Work can reach the first limiter, then collect behind the second before it actually starts.

limiter's boundaries, license, and upgrade surface

The clearest wrong-tool case is concurrent execution control. limiter governs admission and when work starts, but it does not track when asynchronous work finishes and does not cap simultaneous in-flight operations. A service needing a fixed concurrency ceiling needs a separate concurrency limiter. Likewise, a strict moving-time-window requirement should use a rolling-window algorithm rather than `RateLimiter`.

Input handling is strict. Token counts and capacities must be finite, non-negative numbers no greater than `Number.MAX_SAFE_INTEGER`; fractional amounts are valid. Numeric intervals must be finite and greater than zero. Invalid input throws `RangeError`, or causes an asynchronous call to reject. An oversized request returns `false` from `tryRemoveTokens()` and rejects from `removeTokens()`.

The package is published under the MIT license. Its repository and npm package metadata also include the source, changelog, compiled `dist` files, and generated package metadata. Development tooling includes TypeScript, Jest, ESLint, Babel, and Prettier, but these are not runtime dependencies.

The repository is not archived, and the last push was on 2026-09-11. Version `4.1.0` was released on the same date, following `4.0.0` on 2026-09-09. The README identifies `RateLimiter.getWaitTime()` as new in 4.1 and states that `TokenBucket.getWaitTime()` was available in 4.0. The available documentation does not provide a complete migration guide from earlier major versions.

Editorial conclusion

Adopt limiter for request pacing, byte budgets, and token-bucket constraints when one shared Node.js process owns the resource. Do not use it as the sole control for rolling windows or maximum concurrent work. Before adoption, verify the required algorithm, confirm that interval boundary behavior fits the service, and check whether upstream asynchronous operations need a separate concurrency limiter.

Frequently asked questions

How do I install limiter in Node.js?

The README gives `npm install limiter`. The published package supports imports and requires, and its metadata includes TypeScript declarations.

Should I use RateLimiter or TokenBucket in limiter?

Use `RateLimiter` for a maximum token count per interval. Use `TokenBucket` when burst capacity and refill rate are separate requirements, or when a child bucket should share a parent budget.

Does limiter enforce a rolling window or maximum concurrency?

No. `RateLimiter` is explicitly not a rolling-window limiter, and neither limiter model tracks completion of asynchronous work or limits simultaneous in-flight operations.

Official sources

  1. Issues
  2. jhurliman/node-rate-limiter on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jhurliman-node-rate-limiter.svg)](https://hysenlabs.com/projects/jhurliman-node-rate-limiter)