580 lines
19 KiB
Markdown
580 lines
19 KiB
Markdown
# <div align="center"> Express Rate Limit </div>
|
||
|
||
---
|
||
|
||
Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
|
||
Gateway for developers. Add
|
||
[dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
|
||
authentication and more to any API in minutes. Learn more at
|
||
[zuplo.com](https://zuplo.link/express-rate-limit)
|
||
|
||
---
|
||
|
||
<div align="center">
|
||
|
||
[](https://github.com/express-rate-limit/express-rate-limit/actions/workflows/ci.yaml)
|
||
[](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
|
||
[](https://www.npmjs.com/package/express-rate-limit)
|
||
|
||
Basic rate-limiting middleware for [Express](http://expressjs.com/). Use to
|
||
limit repeated requests to public APIs and/or endpoints such as password reset.
|
||
Plays nice with
|
||
[express-slow-down](https://www.npmjs.com/package/express-slow-down).
|
||
|
||
</div>
|
||
|
||
## Use Cases
|
||
|
||
Depending on your use case, you may need to switch to a different
|
||
[store](#store).
|
||
|
||
#### Abuse Prevention
|
||
|
||
The default `MemoryStore` is probably fine.
|
||
|
||
#### API Rate Limit Enforcement
|
||
|
||
You likely want to switch to a different [store](#store). As a performance
|
||
optimization, the default `MemoryStore` uses a global time window, so if your
|
||
limit is 10 requests per minute, a single user might be able to get an initial
|
||
burst of up to 20 requests in a row if they happen to get the first 10 in at the
|
||
end of one minute and the next 10 in at the start of the next minute. (After the
|
||
initial burst, they will be limited to the expected 10 requests per minute.) All
|
||
other stores use per-user time windows, so a user will get exactly 10 requests
|
||
regardless.
|
||
|
||
Additionally, if you have multiple servers or processes (for example, with the
|
||
[node:cluster](https://nodejs.org/api/cluster.html) module), you'll likely want
|
||
to use an external data store to syhcnronize hits
|
||
([redis](https://npmjs.com/package/rate-limit-redis),
|
||
[memcached](https://npmjs.org/package/rate-limit-memcached), [etc.](#store))
|
||
This will guarentee the expected result even if some requests get handled by
|
||
different servers/processes.
|
||
|
||
### Alternate Rate Limiters
|
||
|
||
This module was designed to only handle the basics and didn't even support
|
||
external stores initially. These other options all are excellent pieces of
|
||
software and may be more appropriate for some situations:
|
||
|
||
- [`rate-limiter-flexible`](https://www.npmjs.com/package/rate-limiter-flexible)
|
||
- [`express-brute`](https://www.npmjs.com/package/express-brute)
|
||
- [`rate-limiter`](https://www.npmjs.com/package/express-limiter)
|
||
|
||
## Installation
|
||
|
||
From the npm registry:
|
||
|
||
```sh
|
||
# Using npm
|
||
> npm install express-rate-limit
|
||
# Using yarn or pnpm
|
||
> yarn/pnpm add express-rate-limit
|
||
```
|
||
|
||
From Github Releases:
|
||
|
||
```sh
|
||
# Using npm
|
||
> npm install https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
||
# Using yarn or pnpm
|
||
> yarn/pnpm add https://github.com/express-rate-limit/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
||
```
|
||
|
||
Replace `{version}` with the version of the package that you want to your, e.g.:
|
||
`6.0.0`.
|
||
|
||
## Usage
|
||
|
||
### Importing
|
||
|
||
This library is provided in ESM as well as CJS forms, and works with both
|
||
Javascript and Typescript projects.
|
||
|
||
**This package requires you to use Node 14 or above.**
|
||
|
||
Import it in a CommonJS project (`type: commonjs` or no `type` field in
|
||
`package.json`) as follows:
|
||
|
||
```ts
|
||
const { rateLimit } = require('express-rate-limit')
|
||
```
|
||
|
||
Import it in a ESM project (`type: module` in `package.json`) as follows:
|
||
|
||
```ts
|
||
import { rateLimit } from 'express-rate-limit'
|
||
```
|
||
|
||
### Examples
|
||
|
||
To use it in an API-only server where the rate-limiter should be applied to all
|
||
requests:
|
||
|
||
```ts
|
||
import { rateLimit } from 'express-rate-limit'
|
||
|
||
const limiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
||
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
||
legacyHeaders: false, // X-RateLimit-* headers
|
||
// store: ... , // Use an external store for more precise rate limiting
|
||
})
|
||
|
||
// Apply the rate limiting middleware to all requests
|
||
app.use(limiter)
|
||
```
|
||
|
||
To use it in a 'regular' web server (e.g. anything that uses
|
||
`express.static()`), where the rate-limiter should only apply to certain
|
||
requests:
|
||
|
||
```ts
|
||
import { rateLimit } from 'express-rate-limit'
|
||
|
||
const apiLimiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
||
standardHeaders: 'draft-7', // Set `RateLimit` and `RateLimit-Policy`` headers
|
||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||
// store: ... , // Use an external store for more precise rate limiting
|
||
})
|
||
|
||
// Apply the rate limiting middleware to API calls only
|
||
app.use('/api', apiLimiter)
|
||
```
|
||
|
||
To create multiple instances to apply different rules to different endpoints:
|
||
|
||
```ts
|
||
import { rateLimit } from 'express-rate-limit'
|
||
|
||
const apiLimiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
||
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
||
legacyHeaders: false, // X-RateLimit-* headers
|
||
// store: ... , // Use an external store for more precise rate limiting
|
||
})
|
||
|
||
app.use('/api/', apiLimiter)
|
||
|
||
const createAccountLimiter = rateLimit({
|
||
windowMs: 60 * 60 * 1000, // 1 hour
|
||
max: 5, // Limit each IP to 5 create account requests per `window` (here, per hour)
|
||
message:
|
||
'Too many accounts created from this IP, please try again after an hour',
|
||
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
||
legacyHeaders: false, // X-RateLimit-* headers
|
||
})
|
||
|
||
app.post('/create-account', createAccountLimiter, (request, response) => {
|
||
//...
|
||
})
|
||
```
|
||
|
||
To use a custom store:
|
||
|
||
```ts
|
||
import { rateLimit } from 'express-rate-limit'
|
||
import RedisStore from 'rate-limit-redis'
|
||
import RedisClient from 'ioredis'
|
||
|
||
const redisClient = new RedisClient()
|
||
const rateLimiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
||
standardHeaders: 'draft-7', // draft-6: RateLimit-* headers; draft-7: combined RateLimit header
|
||
legacyHeaders: false, // X-RateLimit-* headers
|
||
store: new RedisStore({
|
||
/* ... */
|
||
}), // Use the external store
|
||
})
|
||
|
||
// Apply the rate limiting middleware to all requests
|
||
app.use(rateLimiter)
|
||
```
|
||
|
||
> **Note:** most stores will require additional configuration, such as custom
|
||
> prefixes, when using multiple instances. The default built-in memory store is
|
||
> an exception to this rule.
|
||
|
||
### Troubleshooting Proxy Issues
|
||
|
||
If you are behind a proxy/load balancer (usually the case with most hosting
|
||
services, e.g. Heroku, Bluemix, AWS ELB, Nginx, Cloudflare, Akamai, Fastly,
|
||
Firebase Hosting, Rackspace LB, Riverbed Stingray, etc.), the IP address of the
|
||
request might be the IP of the load balancer/reverse proxy (making the rate
|
||
limiter effectively a global one and blocking all requests once the limit is
|
||
reached) or `undefined`. To solve this issue, add the following line to your
|
||
code (right after you create the express application):
|
||
|
||
```ts
|
||
app.set('trust proxy', numberOfProxies)
|
||
```
|
||
|
||
Where `numberOfProxies` is the number of proxies between the user and the
|
||
server. To find the correct number, create a test endpoint that returns the
|
||
client IP:
|
||
|
||
```ts
|
||
app.set('trust proxy', 1)
|
||
app.get('/ip', (request, response) => response.send(request.ip))
|
||
```
|
||
|
||
Go to `/ip` and see the IP address returned in the response. If it matches your
|
||
public IP address, then the number of proxies is correct and the rate limiter
|
||
should now work correctly. If not, then keep increasing the number until it
|
||
does.
|
||
|
||
For more information about the `trust proxy` setting, take a look at the
|
||
[official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
|
||
|
||
## Configuration
|
||
|
||
### `windowMs`
|
||
|
||
> `number`
|
||
|
||
Time frame for which requests are checked/remembered. Also used in the
|
||
`Retry-After` header when the limit is reached.
|
||
|
||
Note: with stores that do not implement the `init` function (see the table in
|
||
the [`stores` section below](#stores)), you may need to configure this value
|
||
twice, once here and once on the store. In some cases the units also differ
|
||
(e.g. seconds vs miliseconds).
|
||
|
||
Defaults to `60000` ms (= 1 minute).
|
||
|
||
### `max`
|
||
|
||
> `number | function`
|
||
|
||
The maximum number of connections to allow during the `window` before rate
|
||
limiting the client.
|
||
|
||
Can be the limit itself as a number or a (sync/async) function that accepts the
|
||
Express `request` and `response` objects and then returns a number.
|
||
|
||
Defaults to `5`. Set it to `0` to disable the rate limiter.
|
||
|
||
An example of using a function:
|
||
|
||
```ts
|
||
const isPremium = async (user) => {
|
||
// ...
|
||
}
|
||
|
||
const limiter = rateLimit({
|
||
// ...
|
||
max: async (request, response) => {
|
||
if (await isPremium(request.user)) return 10
|
||
else return 5
|
||
},
|
||
})
|
||
```
|
||
|
||
### `message`
|
||
|
||
> `any`
|
||
|
||
The response body to send back when a client is rate limited.
|
||
|
||
May be a `string`, JSON object, or any other value that Express's
|
||
[`response.send`](https://expressjs.com/en/4x/api.html#res.send) method
|
||
supports. It can also be a (sync/async) function that accepts the Express
|
||
request and response objects and then returns a `string`, JSON object or any
|
||
other value the Express `response.send` function accepts.
|
||
|
||
Defaults to `'Too many requests, please try again later.'`
|
||
|
||
An example of using a function:
|
||
|
||
```ts
|
||
const isPremium = async (user) => {
|
||
// ...
|
||
}
|
||
|
||
const limiter = rateLimit({
|
||
// ...
|
||
message: async (request, response) => {
|
||
if (await isPremium(request.user))
|
||
return 'You can only make 10 requests every hour.'
|
||
else return 'You can only make 5 requests every hour.'
|
||
},
|
||
})
|
||
```
|
||
|
||
### `statusCode`
|
||
|
||
> `number`
|
||
|
||
The HTTP status code to send back when a client is rate limited.
|
||
|
||
Defaults to `429` (HTTP 429 Too Many Requests - RFC 6585).
|
||
|
||
### `legacyHeaders`
|
||
|
||
> `boolean`
|
||
|
||
Whether to send the legacy rate limit headers for the limit
|
||
(`X-RateLimit-Limit`), current usage (`X-RateLimit-Remaining`) and reset time
|
||
(if the store provides it) (`X-RateLimit-Reset`) on all responses. If set to
|
||
`true`, the middleware also sends the `Retry-After` header on all blocked
|
||
requests.
|
||
|
||
Defaults to `true` (for backward compatibility).
|
||
|
||
> Renamed in `6.x` from `headers` to `legacyHeaders`.
|
||
|
||
### `standardHeaders`
|
||
|
||
> `boolean` | `'draft-6'` | `'draft-7'`
|
||
|
||
Whether to enable support for headers conforming to the
|
||
[RateLimit header fields for HTTP standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers)
|
||
adopted by the IETF.
|
||
|
||
If set to `draft-6`, separate `RateLimit-Policy` `RateLimit-Limit`,
|
||
`RateLimit-Remaining`, and, if the store supports it, `RateLimit-Reset` headers
|
||
are set on the response, in accordance with
|
||
[draft-ietf-httpapi-ratelimit-headers-06](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-06).
|
||
|
||
If set to `draft-7`, a combined `RateLimit` header is set containing limit,
|
||
remaining, and reset values, and a `RateLimit-Policy` header is set, in
|
||
accordiance with
|
||
[draft-ietf-httpapi-ratelimit-headers-07](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers-07).
|
||
`windowMs` is used for the reset value if the store does not provide a reset
|
||
time.
|
||
|
||
If set to `true`, it is treated as `draft-6`, however this behavior may change
|
||
in a future semver major release.
|
||
|
||
If set to any truthy value, the middleware also sends the `Retry-After` header
|
||
on all blocked requests.
|
||
|
||
The `standardHeaders` option may be used in conjunction with, or instead of the
|
||
`legacyHeaders` option.
|
||
|
||
ℹ️ Tip: use
|
||
[ratelimit-header-parser](https://www.npmjs.com/package/ratelimit-header-parser)
|
||
in clients to read/parse any form of express-rate-limit's headers.
|
||
|
||
Defaults to `false`.
|
||
|
||
> Renamed in `6.x` from `draft_polli_ratelimit_headers` to `standardHeaders`.
|
||
|
||
### `requestPropertyName`
|
||
|
||
> `string`
|
||
|
||
The name of the property on the Express `request` object to store the rate limit
|
||
info.
|
||
|
||
Defaults to `'rateLimit'`.
|
||
|
||
### `skipFailedRequests`
|
||
|
||
> `boolean`
|
||
|
||
When set to `true`, failed requests won't be counted. Request considered failed
|
||
when the `requestWasSuccessful` option returns `false`. By default, this means
|
||
requests fail when:
|
||
|
||
- the response status >= 400
|
||
- the request was cancelled before last chunk of data was sent (response `close`
|
||
event triggered)
|
||
- the response `error` event was triggered by response
|
||
|
||
(Technically they are counted and then un-counted, so a large number of slow
|
||
requests all at once could still trigger a rate-limit. This may be fixed in a
|
||
future release. PRs welcome!)
|
||
|
||
Defaults to `false`.
|
||
|
||
### `skipSuccessfulRequests`
|
||
|
||
> `boolean`
|
||
|
||
If `true`, the library will (by default) skip all requests that are considered
|
||
'failed' by the `requestWasSuccessful` function. By default, this means requests
|
||
succeed when the response status code < 400.
|
||
|
||
(Technically they are counted and then un-counted, so a large number of slow
|
||
requests all at once could still trigger a rate-limit. This may be fixed in a
|
||
future release. PRs welcome!)
|
||
|
||
Defaults to `false`.
|
||
|
||
### `keyGenerator`
|
||
|
||
> `function`
|
||
|
||
Method to retrieve custom identifiers for clients, such as their IP address,
|
||
username, or API Key.
|
||
|
||
Should be a (sync/async) function that accepts the Express `request` and
|
||
`response` objects and then returns a string.
|
||
|
||
By default, the client's IP address is used:
|
||
|
||
```ts
|
||
const limiter = rateLimit({
|
||
// ...
|
||
keyGenerator: (request, response) => request.ip,
|
||
})
|
||
```
|
||
|
||
> **Note** If a `keyGenerator` returns the same value for every user, it becomes
|
||
> a global rate limiter. This could be combined with a second instance of
|
||
> `express-rate-limit` to have both global and per-user limits.
|
||
|
||
### `handler`
|
||
|
||
> `function`
|
||
|
||
Express request handler that sends back a response when a client is
|
||
rate-limited.
|
||
|
||
By default, sends back the `statusCode` and `message` set via the `options`,
|
||
similar to this:
|
||
|
||
```ts
|
||
const limiter = rateLimit({
|
||
// ...
|
||
handler: (request, response, next, options) =>
|
||
response.status(options.statusCode).send(options.message),
|
||
})
|
||
```
|
||
|
||
### `onLimitReached`
|
||
|
||
> `function`
|
||
|
||
A (sync/async) function that accepts the Express `request` and `response`
|
||
objects that is called the on the request where a client has just exceeded their
|
||
rate limit.
|
||
|
||
This method was
|
||
[deprecated in v6](https://github.com/express-rate-limit/express-rate-limit/releases/v6.0.0) -
|
||
Please use a custom `handler` that checks the number of hits instead.
|
||
|
||
### `skip`
|
||
|
||
> `function`
|
||
|
||
Function to determine whether or not this request counts towards a client's
|
||
quota. Should be a (sync/async) function that accepts the Express `request` and
|
||
`response` objects and then returns `true` or `false`.
|
||
|
||
Could also act as an allowlist for certain keys:
|
||
|
||
```ts
|
||
const allowlist = ['192.168.0.56', '192.168.0.21']
|
||
|
||
const limiter = rateLimit({
|
||
// ...
|
||
skip: (request, response) => allowlist.includes(request.ip),
|
||
})
|
||
```
|
||
|
||
By default, it skips no requests:
|
||
|
||
```ts
|
||
const limiter = rateLimit({
|
||
// ...
|
||
skip: (request, response) => false,
|
||
})
|
||
```
|
||
|
||
### `requestWasSuccessful`
|
||
|
||
> `function`
|
||
|
||
Method to determine whether or not the request counts as 'succesful'. Used when
|
||
either `skipSuccessfulRequests` or `skipFailedRequests` is set to true. Should
|
||
be a (sync/async) function that accepts the Express `request` and `response`
|
||
objects and then returns `true` or `false`.
|
||
|
||
By default, requests with a response status code less than 400 are considered
|
||
successful:
|
||
|
||
```ts
|
||
const limiter = rateLimit({
|
||
// ...
|
||
requestWasSuccessful: (request, response) => response.statusCode < 400,
|
||
})
|
||
```
|
||
|
||
### `validate`
|
||
|
||
> `boolean`
|
||
|
||
When enabled, a set of validation checks are run on the first request to detect
|
||
common misconfigurations with proxies, etc. Prints an error to the console if
|
||
any issue is detected.
|
||
|
||
Automatically disables after the first request is processed.
|
||
|
||
See https://github.com/express-rate-limit/express-rate-limit/wiki/Error-Codes
|
||
for more info.
|
||
|
||
Defaults to true.
|
||
|
||
### `store`
|
||
|
||
> `Store`
|
||
|
||
The `Store` to use to store the hit count for each client.
|
||
|
||
By default, the [`memory-store`](source/memory-store.ts) is used.
|
||
|
||
Here is a list of external stores:
|
||
|
||
| Name | Description | Legacy/Modern |
|
||
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------- |
|
||
| [`memory-store`](source/memory-store.ts) | _(default)_ Simple in-memory option. Does not share state when app has multiple processes or servers. | Modern as of v6.0.0 |
|
||
| [`rate-limit-redis`](https://npmjs.com/package/rate-limit-redis) | A [Redis](http://redis.io/)-backed store, more suitable for large or demanding deployments. | Modern as of v3.0.0 |
|
||
| [`rate-limit-memcached`](https://npmjs.org/package/rate-limit-memcached) | A [Memcached](https://memcached.org/)-backed store. | Legacy |
|
||
| [`rate-limit-mongo`](https://www.npmjs.com/package/rate-limit-mongo) | A [MongoDB](https://www.mongodb.com/)-backed store. | Legacy |
|
||
| [`precise-memory-rate-limit`](https://www.npmjs.com/package/precise-memory-rate-limit) | A memory store similar to the built-in one, except that it stores a distinct timestamp for each key. | Modern as of v2.0.0 |
|
||
|
||
Take a look at
|
||
[this guide](https://github.com/express-rate-limit/express-rate-limit/wiki/Creating-Your-Own-Store)
|
||
if you wish to create your own store.
|
||
|
||
## Request API
|
||
|
||
A `request.rateLimit` property is added to all requests with the `limit`,
|
||
`current`, and `remaining` number of requests and, if the store provides it, a
|
||
`resetTime` Date object. These may be used in your application code to take
|
||
additional actions or inform the user of their status.
|
||
|
||
The property name can be configured with the configuration option
|
||
`requestPropertyName`.
|
||
|
||
## Instance API
|
||
|
||
### `resetKey(key)`
|
||
|
||
Resets the rate limiting for a given key. An example use case is to allow users
|
||
to complete a captcha or whatever to reset their rate limit, then call this
|
||
method.
|
||
|
||
## Issues and Contributing
|
||
|
||
If you encounter a bug or want to see something added/changed, please go ahead
|
||
and
|
||
[open an issue](https://github.com/nfriexpress-rate-limitedly/express-rate-limit/issues/new)!
|
||
If you need help with something, feel free to
|
||
[start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
|
||
|
||
If you wish to contribute to the library, thanks! First, please read
|
||
[the contributing guide](contributing.md). Then you can pick up any issue and
|
||
fix/implement it!
|
||
|
||
## License
|
||
|
||
MIT © [Nathan Friedly](http://nfriedly.com/)
|