# rolling-rate-limiter

> Rate limiter that supports a rolling window, either in-memory or backed by Redis

Latest version **0.4.2** (published 2023-02-14) · MIT license · 0 weekly downloads

## Install

```sh
npm install rolling-rate-limiter
pnpm add rolling-rate-limiter
yarn add rolling-rate-limiter
bun add rolling-rate-limiter
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.4.2 |
| Published | 2023-02-14 |
| First published | 2015-02-05 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Node | >= 12.0.0 |
| Dependencies | 2 |
| Unpacked size | 170.7 KB |
| Known vulnerabilities | 0 (+1 in 1 direct dependencies) |
| Install scripts | no |
| GitHub stars | 368 |
| Author | Peter Hayes |
| Maintainers | peterkhayes |
| Keywords | rate, limiter, ratelimiter, redis, rolling, window |

## Links

- npm: https://www.npmjs.com/package/rolling-rate-limiter
- Repository: https://github.com/peterkhayes/rolling-rate-limiter
- Issues: https://github.com/peterkhayes/rolling-rate-limiter/issues
- npm.io page: https://npm.io/package/rolling-rate-limiter

## Dependencies (2)

- [uuid](https://npm.io/package/uuid.md) ^9.0.0
- [microtime](https://npm.io/package/microtime.md) ^3.0.0

## Alternatives

- [memory-cache](https://npm.io/package/memory-cache.md) — 795.0K weekly downloads
- [@httptoolkit/proxy-agent](https://npm.io/package/@httptoolkit/proxy-agent.md) — 11.2K weekly downloads
- [express-cache-controller](https://npm.io/package/express-cache-controller.md) — 5.3K weekly downloads
- [http-cache-middleware](https://npm.io/package/http-cache-middleware.md) — 4.5K weekly downloads
- [cache2](https://npm.io/package/cache2.md) — 1.5K weekly downloads

## Recent versions

- 0.4.2 (latest) — 2023-02-14
- 0.4.1 — 2023-02-14
- 0.4.0 — 2023-02-14
- 0.3.0 — 2022-09-13
- 0.2.13 — 2021-11-29
- 0.2.12 — 2021-11-18
- 0.2.11 — 2021-04-27
- 0.2.10 — 2021-01-25
- 0.2.9 — 2020-12-04
- 0.2.8 — 2020-09-10
- 0.2.6 — 2020-09-08
- 0.2.5 — 2020-09-08
- 0.2.4 — 2020-09-08
- 0.2.3 — 2020-09-02
- 0.2.2 — 2020-09-02
- … 17 more at https://npm.io/package/rolling-rate-limiter/versions

## README

# Rolling Rate Limiter

![build status](https://github.com/peterkhayes/rolling-rate-limiter/workflows/CI/badge.svg)

This is an implementation of a rate limiter in node.js that allows for rate limiting with a rolling window. It can use either in-memory storage or Redis as a backend. If Redis is used, multiple rate limiters can share one instance with different namespaces, and multiple processes can share rate limiter state safely.

This means that if a user is allowed 5 actions per 60 seconds, any action will be blocked if 5 actions have already occured in the preceeding 60 seconds, without any set points at which this interval resets. This contrasts with some other rate limiter implementations, in which a user could make 5 requests at 0:59 and another 5 requests at 1:01.

**Important Note**:
As a consequence of the way the Redis algorithm works, if an action is blocked, it is still "counted". This means that if a user is continually attempting actions more quickly than the allowed rate, **all** of their actions will be blocked until they pause or slow their requests.

This behavior is somewhat counterintuitive, but it's the only way that I have found that uses an atomic `MULTI` set of commands for Redis. Without this, race conditions would be possible. [See more below.](#method-of-operation).

## Quick start

Basic use in an Express application.

```javascript
const { RedisRateLimiter } = require("rolling-rate-limiter");

const limiter = new RedisRateLimiter({
  client: redisClient, // client instance from `redis` or `ioredis`
  namespace: "rate-limiter", // prefix for redis keys
  interval: 60000, // milliseconds
  maxInInterval: 5,
});

app.use(function (req, res, next) {
  limiter.limit(req.ipAddress).then((wasBlocked) => {
    if (wasBlocked) {
      return res.status(429).send("Too many requests");
    } else {
      return next();
    }
  });
});
```

## Available limiters

- `InMemoryRateLimiter` - Stores state in memory. Useful in testing or outside of web servers.
- Redis rate limiters: There are two main redis clients for node: [redis (aka node-redis)](https://github.com/redis/node-redis) and [ioredis](https://github.com/luin/ioredis). Both are supported:
  - `RedisRateLimiter` - Attempts to detect whether it was passed a `redis` or `ioredis` client.
  - `NodeRedisRateLimiter` - No detection; only works with `redis` client.
  - `IORedisRateLimiter` - No detection; only works with `ioredis` client.

## Configuration options

- `interval: number` - The length of the rate limiter's interval, in milliseconds. For example, if you want a user to be able to perform 5 actions per minute, this should be `60000`.
- `maxInInterval: number` - The number of actions allowed in each interval. For example, in the scenario above, this would be `5`
- `minDifference?: number` - Optional. The minimum time allowed between consecutive actions, in milliseconds.
- `client: Client` (Redis only) - The Redis client to use.
- `namespace: string` (Redis only) - A string to prepend to all keys to prevent conflicts with other code using Redis.

## Instance Methods

All methods take an `Id`, which should be of type `number | string`. Commonly, this will be a user's id.

- `limit(id: Id): Promise<boolean>` - Attempt to perform an action. Returns `false` if the action should be allowed, and `true` if the action should be blocked.
- `wouldLimit(id: Id): Promise<boolean>` - Return what would happen if an action were attempted. Returns `false` if an action would not have been blocked, and `true` if an action would have been blocked. Does not "count" as an action.
- `limitWithInfo(id: Id): Promise<RateLimitInfo>` - Attempt to perform an action. Returns whether the action should be blocked, as well as additional information about why it was blocked and how long the user must wait.
- `wouldLimitWithInfo(id: Id): Promise<RateLimitInfo>` - Returns info about what would happened if an action were attempted and why. Does not "count" as an action.

`RateLimitInfo` contains the following properties:

- `blocked: boolean` - Whether the action was blocked (or would have been blocked).
- `blockedDueToCount: boolean` - Whether the action was blocked (or would have been blocked) because of the `interval` and `maxInInterval` properties.
- `blockedDueToMinDifference: boolean` - Whether the action was blocked (or would have been blocked) because of the `minDistance` property.
- `millisecondsUntilAllowed: number` - The number of milliseconds the user must wait until they can make another action. If another action would immediately be permitted, this is `0`.
- `actionsRemaining: number` - The number of actions a user has left within the interval. Does not account for `minDifference`.

## Method of operation

- Each identifier/user corresponds to a _sorted set_ data structure. The keys and values are both equal to the (microsecond) times at which actions were attempted, allowing easy manipulation of this list.
- When a new action comes in for a user, all elements in the set that occurred earlier than (current time - interval) are dropped from the set.
- If the number of elements in the set is still greater than the maximum, the current action is blocked.
- If a minimum difference has been set and the most recent previous element is too close to the current time, the current action is blocked.
- The current action is then added to the set.
- _Note_: if an action is blocked, it is still added to the set. This means that if a user is continually attempting actions more quickly than the allowed rate, _all_ of their actions will be blocked until they pause or slow their requests.
- If the limiter uses a redis instance, the keys are prefixed with namespace, allowing a single redis instance to support separate rate limiters.
- All redis operations for a single rate-limit check/update are performed as an atomic transaction, allowing rate limiters running on separate processes or machines to share state safely.

## Local development

### Installation

Install dependencies with `yarn`.

To run tests, you will need to have a Redis server running. You can do this by installing Redis, and running `redis-server`. Alternatively, you can run the CI build, which includes tests, by installing [act](https://github.com/nektos/act). This requires Docker to be running - on MacOS that means running `Docker.app` from your `Applications` folder.

### Testing

- `yarn ci`: Runs the CI build, including linting, type checking, and tests. Requires [act](https://github.com/nektos/act) to run GitHub actions locally.
- `yarn lint`: Runs ESLint.
- `yarn test`: Runs Jest.
- `yarn typecheck`: Runs TypeScript, without emitting output.
- `yarn build`: Runs TypeScript and outputs to `./lib`.

---
_Source: https://npm.io/package/rolling-rate-limiter · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
