# rate-limiter-flexible: a Node.js counter library with a unified API across stores

> rate-limiter-flexible counts events and limits access per key, in memory or against Redis, Valkey, Prisma, DynamoDB, Memcached, MongoDB, MySQL, SQLite and PostgreSQL. The API stays the same as you move between them, which is the whole point and also the main thing to check before you commit.

**animir/node-rate-limiter-flexible** — Atomic and non-atomic counters and rate limiting tools. Limit resource access at any scale.

- Repository: https://github.com/animir/node-rate-limiter-flexible
- Stars: 3,589 · Forks: 195
- Language: JavaScript
- License: ISC
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/animir-node-rate-limiter-flexible

## The problem rate-limiter-flexible solves, and for whom

Most Node rate limiting starts as a five-line counter in memory and ends as a distributed problem. The moment you run more than one process, an in-process counter lets each worker admit its own share of traffic, so a limit of 100 requests per minute becomes 100 times the number of workers. Moving the counter into Redis or a database fixes that, but every store has its own increment semantics, its own expiry model and its own client library, and the middleware you picked for the in-memory version usually does not survive the move.

rate-limiter-flexible addresses that specific gap. It is a library of limiter classes that all expose the same methods and return the same result object, whether the counter lives in process memory or in Valkey, Redis, Prisma, DynamoDB, Memcached, MongoDB, MySQL, SQLite or PostgreSQL. The README describes the target audience indirectly through the integrations it lists: Express, Koa, Hapi and NestJS middlewares and plugins, plus GraphQL via graphql-rate-limit-directive. If you are protecting a login endpoint from password brute force, throttling a third-party API client, or capping WebSocket message floods, the shape of the problem is the same and the library expects you to solve it per key.

It is not a policy engine. There is no configuration file that declares routes and their limits. You construct a limiter in code and call consume with whatever string identifies the caller: an IP address, a user ID, an authorisation token or an API route.

## How the counter works and what consume returns

The core mechanism is a points budget. You construct a limiter with points and duration, then consume points against a key. The README's basic example uses six points per second, and the same call can consume more than one point at a time.

```javascript
const opts = {
  points: 6, // 6 points
  duration: 1, // Per second
};

const rateLimiter = new RateLimiterMemory(opts);

rateLimiter.consume(remoteAddress, 2) // consume 2 points
    .then((rateLimiterRes) => {
      // 2 points consumed
    })
    .catch((rateLimiterRes) => {
      // Not enough points to consume
    });
```

The unusual part is that both the resolve and the reject path hand back a RateLimiterRes instance when there is no error. That object carries msBeforeNext, remainingPoints, consumedPoints and isFirstInDuration, which is enough to build the standard rate limit headers without a second lookup. The README shows exactly that, deriving Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset from the result and the options object.

The README states that all operations use atomic increments against race conditions, in memory and in distributed environments alike. That claim is the reason the library exists in this form: a read-then-write counter is wrong under concurrency, and each backing store needs a different primitive to avoid it. The README also names the algorithm: a Flexible Fixed Window that starts counting from the moment a request is received, which spreads reset times across clients instead of letting every client's window expire on the same second.

Beyond consume, the README lists get, set, block, delete, penalty and reward. Penalty and reward are the interesting ones for login flows: you can add points to a key after a failed password attempt and subtract them after a success, which is a different behaviour from a plain request counter.

## Installing rate-limiter-flexible and a first Express-style limit

Installation is a single package. The README gives both npm and yarn forms.

```bash
npm i --save rate-limiter-flexible
```

The import section offers a named import from the package root or a direct path import, which matters if you want to keep the module graph small.

```javascript
import { RateLimiterMemory } from "rate-limiter-flexible";

// or import directly
import RateLimiterMemory from "rate-limiter-flexible/lib/RateLimiterMemory.js";
```

For a first real use, the README's own example is the shortest path: construct a limiter, consume a point keyed on the remote address, and let the rejection path produce a 429 with the headers derived from RateLimiterRes. The README does not spell out an Express handler in the excerpt, but it points to a wiki page titled Express middleware, and it lists copy/paste examples for login endpoint protection and minimal protection against password brute force. Read those before writing your own handler, because the header arithmetic and the block-duration decision are already worked out there.

If you would rather not run a store on day one, RateLimiterMemory needs nothing else. The README also notes that the memory limiter works in the browser, which is unusual for this category and useful if you are throttling client-side actions rather than server traffic. When you do move to a store, the repository ships docker-compose.yml with redis on 6379, valkey on 8080, a single-node valkey-cluster on 8081, dynamodb-local on 8000, postgres on 5432 and etcd on 2379, so you can bring up a test backing store with the dc:up script before touching production.

## Where the design costs you: block strategy, insurance and store choice

The insurance strategy is the most interesting trade-off in the library and the easiest to misconfigure. The README describes it as an emergency solution for when the database or store is down. The wiki page is where the actual behaviour lives; the README only names it. The decision you are making is whether a store outage should fail open, admitting traffic with no limit, or fail closed, rejecting traffic that might be legitimate. Neither answer is free, and the library hands you the choice rather than picking for you.

The second cost is that a distributed limiter is a network round trip on the hot path. The README states an average request takes 0.7ms in Cluster and 2.5ms in a distributed application, and links to benchmarks in the repository. Those numbers are the project's own, and they describe the library's overhead rather than your end-to-end latency; the store's own latency sits on top. If your limit protects an endpoint that already talks to the same Redis instance, the marginal cost is small. If it protects a static asset route, you have added a network hop to a path that had none.

The third cost is conceptual. This library counts. It does not decide which routes get which limits, and it does not give you a declarative policy file. Teams that expect express-rate-limit-style route declarations will find themselves writing more glue code, and the glue is where bugs live. There is also a real ceiling on usefulness: if your application runs as a single process and will continue to, a plain in-memory limiter from any library is sufficient, and the store integrations here are unused surface area.

## rate-limiter-flexible compared with express-rate-limit

The comparison people actually search for is against express-rate-limit, and the difference is architectural rather than a matter of features. express-rate-limit is Express middleware first: you attach it to a route or an app, it reads the client identity, and it sends the 429 for you. Its store interface is a separate concern, with community stores for Redis and others. The default experience is a few lines in your Express setup.

rate-limiter-flexible inverts that. The limiter is a plain object you construct and call, and the framework integration is a thin adapter documented on the wiki for Express, Koa and Hapi, with separate NestJS packages maintained outside this repository. That inversion is what makes the same limiter usable from a WebSocket handler, a background job or a GraphQL directive, none of which are Express routes. It is also why there is no single line to add to your app.

A second difference is the store surface. This repository treats Prisma, Drizzle, DynamoDB, Memcached, MongoDB, MySQL, SQLite and PostgreSQL as first-class backends with their own limiter classes and their own test suites, and package.json shows Prisma and Drizzle schema push steps wired into the test script. That breadth is the library's strongest argument and its largest maintenance surface: each backend is code that has to track its client library's releases. The README's own framing of that cost is the list of interchangeable clients it accepts per store, including valkey-glide or iovalkey, redis or ioredis, and sequelize/typeorm or knex. Pick the client your team already runs, not the one with the newest example.

## Maintenance, licensing and what to verify before adopting

The repository is not archived. Its last push was on 2026-09-17, the same day as the v11.2.1 release, which extended types for RateLimiterValkey options. The two releases before that were v11.2.0 with expiring queue items on 2026-06-08 and v11.1.1 with dynamic execEvenlyMinDelayMs on 2026-06-05. The release cadence visible here is small, typed changes and option additions rather than rewrites, and the version number at 11.x signals a project that has been through many breaking changes already. The README states there are no production dependencies, and TypeScript declarations are bundled, so upgrading the package does not pull a transitive tree with it. The practical upgrade cost is your store client, not this library.

The licence is ISC, a permissive licence in the same family as MIT. The repository carries LICENSE.md at the top level. That is a statement about the licence identifier, not legal advice; if you redistribute the package or bundle it into a product, read LICENSE.md and your own obligations rather than relying on a summary.

One maintenance detail worth noting for anyone tracking this project: the repository ships llms.txt and CONTEXT.md as LLM-oriented documentation, and the README points to them explicitly. That is an unusual choice and a signal about who the maintainer expects to be reading the docs. It does not change the API, but it does mean the README is no longer the only entry point, and the two files may describe the same options differently.

## Conclusion

Adopt rate-limiter-flexible if you want one consume/block/penalty API over Memory today and Redis, Valkey or a SQL store later, and if you can accept that the library gives you counters and a block decision rather than a finished middleware. Skip it if you want per-route declarations in config or a hosted limiter managed for you. Before writing application code, verify two things in the repository: that lib/ contains the limiter class for your chosen store, and which client package that class expects, since the README lists several interchangeable clients per store and the wrong one will not load.

## FAQ

### How do I do rate limiting in Node.js with rate-limiter-flexible?

Construct a limiter with points and duration, then call consume with a key such as an IP address or user ID. The promise resolves with a RateLimiterRes object when points were consumed and rejects with the same object shape when they were not, so both paths can read msBeforeNext and remainingPoints.

### Which rate limiter algorithm does rate-limiter-flexible use?

The README names it the Flexible Fixed Window algorithm, which starts counting from the moment a request is received so that reset times differ across clients. The repository does not present it as a choice between algorithms; it is the algorithm the limiter classes implement.

### What is the purpose of rate-limiter-flexible?

It counts and limits the number of events per key and is positioned as protection against DoS and brute force attacks at scale. It provides a unified API across Memory, Cluster or PM2, Valkey, Redis, Prisma, DynamoDB, Memcached, MongoDB, MySQL, SQLite and PostgreSQL.

## Sources

- [animir/node-rate-limiter-flexible on GitHub](https://github.com/animir/node-rate-limiter-flexible)
- [Issues](https://github.com/animir/node-rate-limiter-flexible/issues)
- [License: ISC](https://github.com/animir/node-rate-limiter-flexible/blob/master/LICENSE)
- [README](https://github.com/animir/node-rate-limiter-flexible/blob/master/README.md)
- [Releases](https://github.com/animir/node-rate-limiter-flexible/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/animir-node-rate-limiter-flexible
