1 | # <div align="center"> Express Rate Limit </div>
|
2 |
|
3 | <div align="center">
|
4 |
|
5 | [![Tests](https://github.com/nfriedly/express-rate-limit/workflows/Test/badge.svg)](https://github.com/nfriedly/express-rate-limit/actions)
|
6 | [![npm version](https://img.shields.io/npm/v/express-rate-limit.svg)](https://npmjs.org/package/express-rate-limit 'View this project on NPM')
|
7 | [![npm downloads](https://img.shields.io/npm/dm/express-rate-limit)](https://www.npmjs.com/package/express-rate-limit)
|
8 |
|
9 | Basic rate-limiting middleware for Express. Use to limit repeated requests to
|
10 | public APIs and/or endpoints such as password reset. Plays nice with
|
11 | [express-slow-down](https://www.npmjs.com/package/express-slow-down).
|
12 |
|
13 | </div>
|
14 |
|
15 | ### Alternate Rate Limiters
|
16 |
|
17 | > This module does not share state with other processes/servers by default. If
|
18 | > you need a more robust solution, I recommend using an external store. See the
|
19 | > [`stores` section](#store) below for a list of external stores.
|
20 |
|
21 | This module was designed to only handle the basics and didn't even support
|
22 | external stores initially. These other options all are excellent pieces of
|
23 | software and may be more appropriate for some situations:
|
24 |
|
25 | - [rate-limiter-flexible](https://www.npmjs.com/package/rate-limiter-flexible)
|
26 | - [express-brute](https://www.npmjs.com/package/express-brute)
|
27 | - [rate-limiter](https://www.npmjs.com/package/express-limiter)
|
28 |
|
29 | ## Install
|
30 |
|
31 | From the npm registry:
|
32 |
|
33 | ```sh
|
34 | # Using npm
|
35 | > npm install express-rate-limit
|
36 | # Using yarn or pnpm
|
37 | > yarn/pnpm add express-rate-limit
|
38 | ```
|
39 |
|
40 | From Github Releases:
|
41 |
|
42 | ```sh
|
43 | # Using npm
|
44 | > npm install https://github.com/nfriedly/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
45 | # Using yarn or pnpm
|
46 | > yarn/pnpm add https://github.com/nfriedly/express-rate-limit/releases/download/v{version}/express-rate-limit.tgz
|
47 | ```
|
48 |
|
49 | Replace `{version}` with the version of the package that you want to your, e.g.:
|
50 | `6.0.0`.
|
51 |
|
52 | ## Usage
|
53 |
|
54 | This library is provided in ESM as well as CJS forms. To import it in a CJS
|
55 | project:
|
56 |
|
57 | ```ts
|
58 | const rateLimit = require('express-rate-limit')
|
59 | ```
|
60 |
|
61 | To import it in a Typescript/ESM project:
|
62 |
|
63 | ```ts
|
64 | import rateLimit from 'express-rate-limit'
|
65 | ```
|
66 |
|
67 | ### Examples
|
68 |
|
69 | To use it in an API-only server where the rate-limiter should be applied to all
|
70 | requests:
|
71 |
|
72 | ```ts
|
73 | import rateLimit from 'express-rate-limit'
|
74 |
|
75 | const limiter = rateLimit({
|
76 | windowMs: 15 * 60 * 1000, // 15 minutes
|
77 | max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
78 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
79 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
80 | })
|
81 |
|
82 | // Apply the rate limiting middleware to all requests
|
83 | app.use(limiter)
|
84 | ```
|
85 |
|
86 | To use it in a 'regular' web server (e.g. anything that uses
|
87 | `express.static()`), where the rate-limiter should only apply to certain
|
88 | requests:
|
89 |
|
90 | ```ts
|
91 | import rateLimit from 'express-rate-limit'
|
92 |
|
93 | const apiLimiter = rateLimit({
|
94 | windowMs: 15 * 60 * 1000, // 15 minutes
|
95 | max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
96 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
97 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
98 | })
|
99 |
|
100 | // Apply the rate limiting middleware to API calls only
|
101 | app.use('/api', apiLimiter)
|
102 | ```
|
103 |
|
104 | To create multiple instances to apply different rules to different endpoints:
|
105 |
|
106 | ```ts
|
107 | import rateLimit from 'express-rate-limit'
|
108 |
|
109 | const apiLimiter = rateLimit({
|
110 | windowMs: 15 * 60 * 1000, // 15 minutes
|
111 | max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
112 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
113 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
114 | })
|
115 |
|
116 | app.use('/api/', apiLimiter)
|
117 |
|
118 | const createAccountLimiter = rateLimit({
|
119 | windowMs: 60 * 60 * 1000, // 1 hour
|
120 | max: 5, // Limit each IP to 5 create account requests per `window` (here, per hour)
|
121 | message:
|
122 | 'Too many accounts created from this IP, please try again after an hour',
|
123 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
124 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
125 | })
|
126 |
|
127 | app.post('/create-account', createAccountLimiter, (request, response) => {
|
128 | //...
|
129 | })
|
130 | ```
|
131 |
|
132 | To use a custom store:
|
133 |
|
134 | ```ts
|
135 | import rateLimit from 'express-rate-limit'
|
136 | import MemoryStore from 'express-rate-limit/memory-store.js'
|
137 |
|
138 | const apiLimiter = rateLimit({
|
139 | windowMs: 15 * 60 * 1000, // 15 minutes
|
140 | max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
|
141 | standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
142 | store: new MemoryStore(),
|
143 | })
|
144 |
|
145 | // Apply the rate limiting middleware to API calls only
|
146 | app.use('/api', apiLimiter)
|
147 | ```
|
148 |
|
149 | > **Note:** most stores will require additional configuration, such as custom
|
150 | > prefixes, when using multiple instances. The default built-in memory store is
|
151 | > an exception to this rule.
|
152 |
|
153 | ### Troubleshooting Proxy Issues
|
154 |
|
155 | If you are behind a proxy/load balancer (usually the case with most hosting
|
156 | services, e.g. Heroku, Bluemix, AWS ELB, Nginx, Cloudflare, Akamai, Fastly,
|
157 | Firebase Hosting, Rackspace LB, Riverbed Stingray, etc.), the IP address of the
|
158 | request might be the IP of the load balancer/reverse proxy (making the rate
|
159 | limiter effectively a global one and blocking all requests once the limit is
|
160 | reached) or `undefined`. To solve this issue, add the following line to your
|
161 | code (right after you create the express application):
|
162 |
|
163 | ```ts
|
164 | app.set('trust proxy', numberOfProxies)
|
165 | ```
|
166 |
|
167 | Where `numberOfProxies` is the number of proxies between the user and the
|
168 | server. To find the correct number, create a test endpoint that returns the
|
169 | client IP:
|
170 |
|
171 | ```ts
|
172 | app.set('trust proxy', 1)
|
173 | app.get('/ip', (request, response) => response.send(request.ip))
|
174 | ```
|
175 |
|
176 | Go to `/ip` and see the IP address returned in the response. If it matches your
|
177 | IP address (which you can get by going to http://ip.nfriedly.com/ or
|
178 | https://api.ipify.org/), then the number of proxies is correct and the rate
|
179 | limiter should now work correctly. If not, then keep increasing the number until
|
180 | it does.
|
181 |
|
182 | For more information about the `trust proxy` setting, take a look at the
|
183 | [official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
|
184 |
|
185 | ## Request API
|
186 |
|
187 | A `request.rateLimit` property is added to all requests with the `limit`,
|
188 | `current`, and `remaining` number of requests and, if the store provides it, a
|
189 | `resetTime` Date object. These may be used in your application code to take
|
190 | additional actions or inform the user of their status.
|
191 |
|
192 | The property name can be configured with the configuration option
|
193 | `requestPropertyName`
|
194 |
|
195 | ## Configuration options
|
196 |
|
197 | ### `windowMs`
|
198 |
|
199 | Time frame for which requests are checked/remembered. Also used in the
|
200 | `Retry-After` header when the limit is reached.
|
201 |
|
202 | Note: with non-default stores, you may need to configure this value twice, once
|
203 | here and once on the store. In some cases the units also differ (e.g. seconds vs
|
204 | miliseconds)
|
205 |
|
206 | Defaults to `60000` ms (= 1 minute).
|
207 |
|
208 | ### `max`
|
209 |
|
210 | Max number of connections during `windowMs` milliseconds before sending a 429
|
211 | response.
|
212 |
|
213 | May be a number, or a function that returns a number or a promise. If `max` is a
|
214 | function, it will be called with `request` and `response` params.
|
215 |
|
216 | Defaults to `5`. Set to `0` to disable.
|
217 |
|
218 | Example of using a function:
|
219 |
|
220 | ```ts
|
221 | import rateLimit from 'express-rate-limit'
|
222 |
|
223 | const isPremium = (request) => {
|
224 | // ...
|
225 | }
|
226 |
|
227 | const limiter = rateLimit({
|
228 | // `max` could also be an async function or return a promise
|
229 | max: (request, response) => {
|
230 | if (isPremium(request)) return 10
|
231 | else return 5
|
232 | },
|
233 | // ...
|
234 | })
|
235 |
|
236 | // Apply the rate limiting middleware to all requests
|
237 | app.use(limiter)
|
238 | ```
|
239 |
|
240 | ### `message`
|
241 |
|
242 | Error message sent to user when `max` is exceeded.
|
243 |
|
244 | May be a `string`, JSON object, or any other value that Express's
|
245 | [response.send](https://expressjs.com/en/4x/api.html#response.send) method
|
246 | supports.
|
247 |
|
248 | Defaults to `'Too many requests, please try again later.'`
|
249 |
|
250 | ### `statusCode`
|
251 |
|
252 | HTTP status code returned when `max` is exceeded.
|
253 |
|
254 | Defaults to `429`.
|
255 |
|
256 | ### `legacyHeaders`
|
257 |
|
258 | Enable headers for request limit (`X-RateLimit-Limit`) and current usage
|
259 | (`X-RateLimit-Remaining`) on all responses and time to wait before retrying
|
260 | (`Retry-After`) when `max` is exceeded.
|
261 |
|
262 | Defaults to `true`.
|
263 |
|
264 | > Renamed in `6.x` from `headers` to `legacyHeaders`.
|
265 |
|
266 | ### `standardHeaders`
|
267 |
|
268 | Enable headers conforming to the
|
269 | [ratelimit standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers/blob/main/draft-ietf-httpapi-ratelimit-headers.md)
|
270 | adopted by the IETF: `RateLimit-Limit`, `RateLimit-Remaining`, and, if the store
|
271 | supports it, `RateLimit-Reset`. May be used in conjunction with, or instead of
|
272 | the `legacyHeaders` option.
|
273 |
|
274 | This setting also enables the `Retry-After` header when `max` is exceeded.
|
275 |
|
276 | Defaults to `false` (for backward compatibility), but recommended to use.
|
277 |
|
278 | > Renamed in `6.x` from `draft_polli_ratelimit_headers` to `standardHeaders`.
|
279 |
|
280 | ### `keyGenerator`
|
281 |
|
282 | Function used to generate keys.
|
283 |
|
284 | Defaults to `request.ip`, similar to this:
|
285 |
|
286 | ```ts
|
287 | const keyGenerator = (request /*, response*/) => request.ip
|
288 | ```
|
289 |
|
290 | ### `handler`
|
291 |
|
292 | The function to handle requests once the max limit is exceeded. It receives the
|
293 | `request` and the `response` objects. The `next` param is available if you need
|
294 | to pass to the next middleware/route. Finally, the `options` param has all of
|
295 | the options that originally passed in when creating the current limiter and the
|
296 | default values for other options.
|
297 |
|
298 | The `request.rateLimit` object has `limit`, `current`, and `remaining` number of
|
299 | requests and, if the store provides it, a `resetTime` Date object.
|
300 |
|
301 | Defaults to:
|
302 |
|
303 | ```ts
|
304 | const handler = (request, response, next, options) => {
|
305 | response.status(options.statusCode).send(options.message)
|
306 | }
|
307 | ```
|
308 |
|
309 | ### `requestWasSuccessful`
|
310 |
|
311 | Function that is called when `skipFailedRequests` and/or
|
312 | `skipSuccessfulRequests` are set to `true`. May be overridden if, for example, a
|
313 | service sends out a 200 status code on errors.
|
314 |
|
315 | Defaults to
|
316 |
|
317 | ```ts
|
318 | const requestWasSuccessful = (request, response) => response.statusCode < 400
|
319 | ```
|
320 |
|
321 | ### `skipFailedRequests`
|
322 |
|
323 | When set to `true`, failed requests won't be counted. Request considered failed
|
324 | when:
|
325 |
|
326 | - response status >= 400
|
327 | - requests that were cancelled before last chunk of data was sent (response
|
328 | `close` event triggered)
|
329 | - response `error` event was triggered by response
|
330 |
|
331 | (Technically they are counted and then un-counted, so a large number of slow
|
332 | requests all at once could still trigger a rate-limit. This may be fixed in a
|
333 | future release.)
|
334 |
|
335 | Defaults to `false`.
|
336 |
|
337 | ### `skipSuccessfulRequests`
|
338 |
|
339 | When set to `true` successful requests (response status < 400) won't be counted.
|
340 | (Technically they are counted and then un-counted, so a large number of slow
|
341 | requests all at once could still trigger a rate-limit. This may be fixed in a
|
342 | future release.)
|
343 |
|
344 | Defaults to `false`.
|
345 |
|
346 | ### `skip`
|
347 |
|
348 | Function used to skip (whitelist) requests. Returning `true`, or a promise that
|
349 | resolves with `true`, from the function will skip limiting for that request.
|
350 |
|
351 | Defaults to always `false` (count all requests):
|
352 |
|
353 | ```ts
|
354 | const skip = (/*request, response*/) => false
|
355 | ```
|
356 |
|
357 | ### `requestPropertyName`
|
358 |
|
359 | The name of the property that contains the rate limit information to add to the
|
360 | `request` object.
|
361 |
|
362 | Defaults to `rateLimit`.
|
363 |
|
364 | ### `store`
|
365 |
|
366 | The storage to use when persisting rate limit attempts.
|
367 |
|
368 | By default, the [memory store](source/memory-store.ts) is used.
|
369 |
|
370 | Available data stores are:
|
371 |
|
372 | - [memory-store](source/memory-store.ts): _(default)_ Simple in-memory option.
|
373 | Does not share state when app has multiple processes or servers.
|
374 | - [rate-limit-redis](https://npmjs.com/package/rate-limit-redis): A
|
375 | [Redis](http://redis.io/)-backed store, more suitable for large or demanding
|
376 | deployments.
|
377 | - [rate-limit-memcached](https://npmjs.org/package/rate-limit-memcached): A
|
378 | [Memcached](https://memcached.org/)-backed store.
|
379 | - [rate-limit-mongo](https://www.npmjs.com/package/rate-limit-mongo): A
|
380 | [MongoDB](https://www.mongodb.com/)-backed store.
|
381 | - [precise-memory-rate-limit](https://www.npmjs.com/package/precise-memory-rate-limit) -
|
382 | A memory store similar to the built-in one, except that it stores a distinct
|
383 | timestamp for each IP rather than bucketing them together.
|
384 |
|
385 | You may also create your own store. It must implement the `Store` interface as
|
386 | follows:
|
387 |
|
388 | ```ts
|
389 | import rateLimit, {
|
390 | Store,
|
391 | Options,
|
392 | IncrementResponse,
|
393 | } from 'express-rate-limit'
|
394 |
|
395 | /**
|
396 | * A {@link Store} that stores the hit count for each client.
|
397 | *
|
398 | * @public
|
399 | */
|
400 | class SomeStore implements Store {
|
401 | /**
|
402 | * Some store-specific parameter.
|
403 | */
|
404 | customParam!: string
|
405 | /**
|
406 | * The duration of time before which all hit counts are reset (in milliseconds).
|
407 | */
|
408 | windowMs!: number
|
409 |
|
410 | /**
|
411 | * @constructor for {@link SomeStore}. Only required if the user needs to pass
|
412 | * some store specific parameters. For example, in a Mongo Store, the user will
|
413 | * need to pass the URI, username and password for the Mongo database.
|
414 | *
|
415 | * @param customParam {string} - Some store-specific parameter.
|
416 | */
|
417 | constructor(customParam: string) {
|
418 | this.customParam = customParam
|
419 | }
|
420 |
|
421 | /**
|
422 | * Method that actually initializes the store. Must be synchronous.
|
423 | *
|
424 | * @param options {Options} - The options used to setup the middleware.
|
425 | *
|
426 | * @public
|
427 | */
|
428 | init(options: Options): void {
|
429 | this.windowMs = options.windowMs
|
430 |
|
431 | // ...
|
432 | }
|
433 |
|
434 | /**
|
435 | * Method to increment a client's hit counter.
|
436 | *
|
437 | * @param key {string} - The identifier for a client
|
438 | *
|
439 | * @returns {IncrementResponse} - The number of hits and reset time for that client
|
440 | *
|
441 | * @public
|
442 | */
|
443 | async increment(key: string): Promise<IncrementResponse> {
|
444 | // ...
|
445 |
|
446 | return {
|
447 | totalHits,
|
448 | resetTime,
|
449 | }
|
450 | }
|
451 |
|
452 | /**
|
453 | * Method to decrement a client's hit counter.
|
454 | *
|
455 | * @param key {string} - The identifier for a client
|
456 | *
|
457 | * @public
|
458 | */
|
459 | async decrement(key: string): Promise<void> {
|
460 | // ...
|
461 | }
|
462 |
|
463 | /**
|
464 | * Method to reset a client's hit counter.
|
465 | *
|
466 | * @param key {string} - The identifier for a client
|
467 | *
|
468 | * @public
|
469 | */
|
470 | async resetKey(key: string): Promise<void> {
|
471 | // ...
|
472 | }
|
473 |
|
474 | /**
|
475 | * Method to reset everyone's hit counter.
|
476 | *
|
477 | * @public
|
478 | */
|
479 | async resetAll(): Promise<void> {
|
480 | // ...
|
481 | }
|
482 | }
|
483 |
|
484 | export default SomeStore
|
485 | ```
|
486 |
|
487 | ## Instance API
|
488 |
|
489 | ### `resetKey(key)`
|
490 |
|
491 | Resets the rate limiting for a given key. An example use case is to allow users
|
492 | to complete a captcha or whatever to reset their rate limit, then call this
|
493 | method.
|
494 |
|
495 | ## Issues and Contributing
|
496 |
|
497 | If you encounter a bug or want to see something added/changed, please go ahead
|
498 | and [open an issue](https://github.com/nfriedly/express-rate-limit/issues/new)!
|
499 | If you need help with something, feel free to
|
500 | [start a discussion](https://github.com/nfriedly/express-rate-limit/discussions/new)!
|
501 |
|
502 | If you wish to contribute to the library, thanks! First, please read
|
503 | [the contributing guide](contributing.md). Then you can pick up any issue and
|
504 | fix/implement it!
|
505 |
|
506 | ## License
|
507 |
|
508 | MIT © [Nathan Friedly](http://nfriedly.com/)
|