express-rate-limit 6.0.4 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.md CHANGED
@@ -6,12 +6,56 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [6.2.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.2.0)
10
+
11
+ ### Added
12
+
13
+ - Export the `MemoryStore`, so it can now be imported as a named import
14
+ (`import { MemoryStore } from 'express-rate-limit'`).
15
+
16
+ ### Fixed
17
+
18
+ - Deprecate the `onLimitReached` option (this was supposed to be deprecated in
19
+ v6.0.0 itself); developers should use a custom handler function that checks if
20
+ the rate limit has been exceeded instead.
21
+
22
+ ## [6.1.0](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.1.0)
23
+
24
+ ### Added
25
+
26
+ - Added a named export `rateLimit` in case the default import does not work.
27
+
28
+ ### Fixed
29
+
30
+ - Added a named export `default`, so Typescript CommonJS developers can
31
+ default-import the library (`import rateLimit from 'express-rate-limit'`).
32
+
33
+ ## [6.0.5](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.5)
34
+
35
+ ### Fixed
36
+
37
+ - Use named imports for ExpressJS types so users do not need to enable the
38
+ `esModuleInterop` flag in their Typescript compiler configuration.
39
+
40
+ ## [6.0.4](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.4)
41
+
42
+ ### Fixed
43
+
44
+ - Upload the built package as a `.tgz` to GitHub releases.
45
+
46
+ ### Changed
47
+
48
+ - Add ` main` and `module` fields to `package.json`. This helps tools such as
49
+ ESLint that do not yet support the `exports` field.
50
+ - Bumped the minimum node.js version in `package-lock.json` to match
51
+ `package.json`
52
+
9
53
  ## [6.0.3](https://github.com/nfriedly/express-rate-limit/releases/tag/v6.0.3)
10
54
 
11
55
  ### Changed
12
56
 
13
- - Bumped minimum Node version from 12.9 to 14.5 because the transpiled output
14
- uses the nullish coalescing operator (`??`), which
57
+ - Bumped minimum Node version from 12.9 to 14.5 in `package.json` because the
58
+ transpiled output uses the nullish coalescing operator (`??`), which
15
59
  [isn't supported in node.js prior to 14.x](https://node.green/#ES2020-features--nullish-coalescing-operator-----).
16
60
 
17
61
  ## [6.0.2](https://github.com/nfriedly/express-rate-limit/releases/v6.0.2)
package/dist/index.cjs CHANGED
@@ -24,7 +24,9 @@ var __toCommonJS = /* @__PURE__ */ ((cache) => {
24
24
  // source/index.ts
25
25
  var source_exports = {};
26
26
  __export(source_exports, {
27
- default: () => source_default
27
+ MemoryStore: () => MemoryStore,
28
+ default: () => lib_default,
29
+ rateLimit: () => lib_default
28
30
  });
29
31
 
30
32
  // source/memory-store.ts
@@ -99,9 +101,9 @@ var promisifyStore = (passedStore) => {
99
101
  return new PromisifiedStore();
100
102
  };
101
103
  var parseOptions = (passedOptions) => {
102
- const options = {
104
+ const notUndefinedOptions = omitUndefinedOptions(passedOptions);
105
+ const config = {
103
106
  windowMs: 60 * 1e3,
104
- store: new MemoryStore(),
105
107
  max: 5,
106
108
  message: "Too many requests, please try again later.",
107
109
  statusCode: 429,
@@ -119,17 +121,17 @@ var parseOptions = (passedOptions) => {
119
121
  return request.ip;
120
122
  },
121
123
  handler: (_request, response, _next, _optionsUsed) => {
122
- response.status(options.statusCode).send(options.message);
124
+ response.status(config.statusCode).send(config.message);
123
125
  },
124
126
  onLimitReached: (_request, _response, _optionsUsed) => {
125
127
  },
126
- ...passedOptions
128
+ ...notUndefinedOptions,
129
+ store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore())
127
130
  };
128
- if (typeof options.store.incr !== "function" && typeof options.store.increment !== "function" || typeof options.store.decrement !== "function" || typeof options.store.resetKey !== "function" || typeof options.store.resetAll !== "undefined" && typeof options.store.resetAll !== "function" || typeof options.store.init !== "undefined" && typeof options.store.init !== "function") {
131
+ if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
129
132
  throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
130
133
  }
131
- options.store = promisifyStore(options.store);
132
- return options;
134
+ return config;
133
135
  };
134
136
  var handleAsyncErrors = (fn) => async (request, response, next) => {
135
137
  try {
@@ -218,9 +220,16 @@ var rateLimit = (passedOptions) => {
218
220
  middleware.resetKey = options.store.resetKey.bind(options.store);
219
221
  return middleware;
220
222
  };
223
+ var omitUndefinedOptions = (passedOptions) => {
224
+ const omittedOptions = {};
225
+ for (const k of Object.keys(passedOptions)) {
226
+ const key = k;
227
+ if (passedOptions[key] !== void 0) {
228
+ omittedOptions[key] = passedOptions[key];
229
+ }
230
+ }
231
+ return omittedOptions;
232
+ };
221
233
  var lib_default = rateLimit;
222
-
223
- // source/index.ts
224
- var source_default = lib_default;
225
234
  module.exports = __toCommonJS(source_exports);
226
- module.exports = rateLimit;
235
+ module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;
package/dist/index.d.ts CHANGED
@@ -1,50 +1,50 @@
1
- // Generated by dts-bundle-generator v6.2.0
1
+ // Generated by dts-bundle-generator v6.4.0
2
2
 
3
- import Express from 'express';
3
+ import { NextFunction, Request, RequestHandler, Response } from 'express';
4
4
 
5
5
  /**
6
6
  * Callback that fires when a client's hit counter is incremented.
7
7
  *
8
- * @param error {Error | undefined} - The error that occurred, if any
9
- * @param totalHits {number} - The number of hits for that client so far
10
- * @param resetTime {Date | undefined} - The time when the counter resets
8
+ * @param error {Error | undefined} - The error that occurred, if any.
9
+ * @param totalHits {number} - The number of hits for that client so far.
10
+ * @param resetTime {Date | undefined} - The time when the counter resets.
11
11
  */
12
12
  export declare type IncrementCallback = (error: Error | undefined, totalHits: number, resetTime: Date | undefined) => void;
13
13
  /**
14
14
  * Method (in the form of middleware) to generate/retrieve a value based on the
15
- * incoming request
15
+ * incoming request.
16
16
  *
17
- * @param request {Express.Request} - The Express request object
18
- * @param response {Express.Response} - The Express response object
17
+ * @param request {Request} - The Express request object.
18
+ * @param response {Response} - The Express response object.
19
19
  *
20
- * @returns {T} - The value needed
20
+ * @returns {T} - The value needed.
21
21
  */
22
- export declare type ValueDeterminingMiddleware<T> = (request: Express.Request, response: Express.Response) => T | Promise<T>;
22
+ export declare type ValueDeterminingMiddleware<T> = (request: Request, response: Response) => T | Promise<T>;
23
23
  /**
24
24
  * Express request handler that sends back a response when a client is
25
25
  * rate-limited.
26
26
  *
27
- * @param request {Express.Request} - The Express request object
28
- * @param response {Express.Response} - The Express response object
29
- * @param next {Express.NextFunction} - The Express `next` function, can be called to skip responding
30
- * @param optionsUsed {Options} - The options used to set up the middleware
27
+ * @param request {Request} - The Express request object.
28
+ * @param response {Response} - The Express response object.
29
+ * @param next {NextFunction} - The Express `next` function, can be called to skip responding.
30
+ * @param optionsUsed {Options} - The options used to set up the middleware.
31
31
  */
32
- export declare type RateLimitExceededEventHandler = (request: Express.Request, response: Express.Response, next: Express.NextFunction, optionsUsed: Options) => void;
32
+ export declare type RateLimitExceededEventHandler = (request: Request, response: Response, next: NextFunction, optionsUsed: Options) => void;
33
33
  /**
34
34
  * Event callback that is triggered on a client's first request that exceeds the limit
35
35
  * but not for subsequent requests. May be used for logging, etc. Should *not*
36
36
  * send a response.
37
37
  *
38
- * @param request {Express.Request} - The Express request object
39
- * @param response {Express.Response} - The Express response object
40
- * @param optionsUsed {Options} - The options used to set up the middleware
38
+ * @param request {Request} - The Express request object.
39
+ * @param response {Response} - The Express response object.
40
+ * @param optionsUsed {Options} - The options used to set up the middleware.
41
41
  */
42
- export declare type RateLimitReachedEventHandler = (request: Express.Request, response: Express.Response, optionsUsed: Options) => void;
42
+ export declare type RateLimitReachedEventHandler = (request: Request, response: Response, optionsUsed: Options) => void;
43
43
  /**
44
44
  * Data returned from the `Store` when a client's hit counter is incremented.
45
45
  *
46
- * @property totalHits {number} - The number of hits for that client so far
47
- * @property resetTime {Date | undefined} - The time when the counter resets
46
+ * @property totalHits {number} - The number of hits for that client so far.
47
+ * @property resetTime {Date | undefined} - The time when the counter resets.
48
48
  */
49
49
  export declare type IncrementResponse = {
50
50
  totalHits: number;
@@ -53,11 +53,11 @@ export declare type IncrementResponse = {
53
53
  /**
54
54
  * A modified Express request handler with the rate limit functions.
55
55
  */
56
- export declare type RateLimitRequestHandler = Express.RequestHandler & {
56
+ export declare type RateLimitRequestHandler = RequestHandler & {
57
57
  /**
58
58
  * Method to reset a client's hit counter.
59
59
  *
60
- * @param key {string} - The identifier for a client
60
+ * @param key {string} - The identifier for a client.
61
61
  */
62
62
  resetKey: (key: string) => void;
63
63
  };
@@ -70,20 +70,20 @@ export interface LegacyStore {
70
70
  /**
71
71
  * Method to increment a client's hit counter.
72
72
  *
73
- * @param key {string} - The identifier for a client
74
- * @param callback {IncrementCallback} - The callback to call once the counter is incremented
73
+ * @param key {string} - The identifier for a client.
74
+ * @param callback {IncrementCallback} - The callback to call once the counter is incremented.
75
75
  */
76
76
  incr: (key: string, callback: IncrementCallback) => void;
77
77
  /**
78
78
  * Method to decrement a client's hit counter.
79
79
  *
80
- * @param key {string} - The identifier for a client
80
+ * @param key {string} - The identifier for a client.
81
81
  */
82
82
  decrement: (key: string) => void;
83
83
  /**
84
84
  * Method to reset a client's hit counter.
85
85
  *
86
- * @param key {string} - The identifier for a client
86
+ * @param key {string} - The identifier for a client.
87
87
  */
88
88
  resetKey: (key: string) => void;
89
89
  /**
@@ -99,27 +99,27 @@ export interface Store {
99
99
  * Method that initializes the store, and has access to the options passed to
100
100
  * the middleware too.
101
101
  *
102
- * @param options {Options} - The options used to setup the middleware
102
+ * @param options {Options} - The options used to setup the middleware.
103
103
  */
104
104
  init?: (options: Options) => void;
105
105
  /**
106
106
  * Method to increment a client's hit counter.
107
107
  *
108
- * @param key {string} - The identifier for a client
108
+ * @param key {string} - The identifier for a client.
109
109
  *
110
- * @returns {IncrementResponse} - The number of hits and reset time for that client
110
+ * @returns {IncrementResponse} - The number of hits and reset time for that client.
111
111
  */
112
112
  increment: (key: string) => Promise<IncrementResponse> | IncrementResponse;
113
113
  /**
114
114
  * Method to decrement a client's hit counter.
115
115
  *
116
- * @param key {string} - The identifier for a client
116
+ * @param key {string} - The identifier for a client.
117
117
  */
118
118
  decrement: (key: string) => Promise<void> | void;
119
119
  /**
120
120
  * Method to reset a client's hit counter.
121
121
  *
122
- * @param key {string} - The identifier for a client
122
+ * @param key {string} - The identifier for a client.
123
123
  */
124
124
  resetKey: (key: string) => Promise<void> | void;
125
125
  /**
@@ -133,18 +133,24 @@ export interface Store {
133
133
  export interface Options {
134
134
  /**
135
135
  * How long we should remember the requests.
136
+ *
137
+ * Defaults to `60000` ms (= 1 minute).
136
138
  */
137
139
  readonly windowMs: number;
138
140
  /**
139
- * The maximum number of connection to allow during the `window` before
141
+ * The maximum number of connections to allow during the `window` before
140
142
  * rate limiting the client.
141
143
  *
142
144
  * Can be the limit itself as a number or express middleware that parses
143
145
  * the request and then figures out the limit.
146
+ *
147
+ * Defaults to `5`.
144
148
  */
145
149
  readonly max: number | ValueDeterminingMiddleware<number>;
146
150
  /**
147
151
  * The response body to send back when a client is rate limited.
152
+ *
153
+ * Defaults to `'Too many requests, please try again later.'`
148
154
  */
149
155
  readonly message: any;
150
156
  /**
@@ -156,10 +162,14 @@ export interface Options {
156
162
  /**
157
163
  * Whether to send `X-RateLimit-*` headers with the rate limit and the number
158
164
  * of requests.
165
+ *
166
+ * Defaults to `true` (for backward compatibility).
159
167
  */
160
168
  readonly legacyHeaders: boolean;
161
169
  /**
162
- * Whether to enable support for the rate limit standardization headers (`RateLimit-*`).
170
+ * Whether to enable support for the standardized rate limit headers (`RateLimit-*`).
171
+ *
172
+ * Defaults to `false` (for backward compatibility, but its use is recommended).
163
173
  */
164
174
  readonly standardHeaders: boolean;
165
175
  /**
@@ -171,43 +181,59 @@ export interface Options {
171
181
  /**
172
182
  * If `true`, the library will (by default) skip all requests that have a 4XX
173
183
  * or 5XX status.
184
+ *
185
+ * Defaults to `false`.
174
186
  */
175
187
  readonly skipFailedRequests: boolean;
176
188
  /**
177
189
  * If `true`, the library will (by default) skip all requests that have a
178
190
  * status code less than 400.
191
+ *
192
+ * Defaults to `false`.
179
193
  */
180
194
  readonly skipSuccessfulRequests: boolean;
181
- /**
182
- * Method to determine whether or not the request counts as 'succesful'. Used
183
- * when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
184
- */
185
- readonly requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
186
195
  /**
187
196
  * Method to generate custom identifiers for clients.
188
197
  *
189
198
  * By default, the client's IP address is used.
190
199
  */
191
200
  readonly keyGenerator: ValueDeterminingMiddleware<string>;
192
- /**
193
- * Method (in the form of middleware) to determine whether or not this request
194
- * counts towards a client's quota.
195
- */
196
- readonly skip: ValueDeterminingMiddleware<boolean>;
197
201
  /**
198
202
  * Express request handler that sends back a response when a client is
199
203
  * rate-limited.
204
+ *
205
+ * By default, sends back the `statusCode` and `message` set via the options.
200
206
  */
201
207
  readonly handler: RateLimitExceededEventHandler;
202
208
  /**
203
209
  * Express request handler that sends back a response when a client has
204
210
  * reached their rate limit, and will be rate limited on their next request.
211
+ *
212
+ * @deprecated 6.x - Please use a custom `handler` that checks the number of
213
+ * hits instead.
205
214
  */
206
215
  readonly onLimitReached: RateLimitReachedEventHandler;
207
216
  /**
208
- * The {@link Store} to use to store the hit count for each client.
217
+ * Method (in the form of middleware) to determine whether or not this request
218
+ * counts towards a client's quota.
219
+ *
220
+ * By default, skips no requests.
221
+ */
222
+ readonly skip: ValueDeterminingMiddleware<boolean>;
223
+ /**
224
+ * Method to determine whether or not the request counts as 'succesful'. Used
225
+ * when either `skipSuccessfulRequests` or `skipFailedRequests` is set to true.
226
+ *
227
+ * By default, requests with a response status code less than 400 are considered
228
+ * successful.
209
229
  */
210
- store: Store;
230
+ readonly requestWasSuccessful: ValueDeterminingMiddleware<boolean>;
231
+ /**
232
+ * The `Store` to use to store the hit count for each client.
233
+ *
234
+ * By default, the built-in `MemoryStore` will be used.
235
+ */
236
+ store: Store | LegacyStore;
211
237
  /**
212
238
  * Whether to send `X-RateLimit-*` headers with the rate limit and the number
213
239
  * of requests.
@@ -227,7 +253,7 @@ export interface Options {
227
253
  * The extended request object that includes information about the client's
228
254
  * rate limit.
229
255
  */
230
- export declare type AugmentedRequest = Express.Request & {
256
+ export declare type AugmentedRequest = Request & {
231
257
  [key: string]: RateLimitInfo;
232
258
  };
233
259
  /**
@@ -240,9 +266,79 @@ export interface RateLimitInfo {
240
266
  readonly remaining: number;
241
267
  readonly resetTime: Date | undefined;
242
268
  }
243
- declare const rateLimit: (passedOptions?: (Omit<Partial<Options>, "store"> & {
244
- store?: LegacyStore | Store | undefined;
245
- }) | undefined) => RateLimitRequestHandler;
246
- export default rateLimit;
269
+ /**
270
+ *
271
+ * Create an instance of IP rate-limiting middleware for Express.
272
+ *
273
+ * @param passedOptions {Options} - Options to configure the rate limiter.
274
+ *
275
+ * @returns {RateLimitRequestHandler} - The middleware that rate-limits clients based on your configuration.
276
+ *
277
+ * @public
278
+ */
279
+ export declare const rateLimit: (passedOptions?: Partial<Options> | undefined) => RateLimitRequestHandler;
280
+ /**
281
+ * A `Store` that stores the hit count for each client in memory.
282
+ *
283
+ * @public
284
+ */
285
+ export declare class MemoryStore implements Store {
286
+ /**
287
+ * The duration of time before which all hit counts are reset (in milliseconds).
288
+ */
289
+ windowMs: number;
290
+ /**
291
+ * The map that stores the number of hits for each client in memory.
292
+ */
293
+ hits: {
294
+ [key: string]: number | undefined;
295
+ };
296
+ /**
297
+ * The time at which all hit counts will be reset.
298
+ */
299
+ resetTime: Date;
300
+ /**
301
+ * Method that initializes the store.
302
+ *
303
+ * @param options {Options} - The options used to setup the middleware.
304
+ */
305
+ init(options: Options): void;
306
+ /**
307
+ * Method to increment a client's hit counter.
308
+ *
309
+ * @param key {string} - The identifier for a client.
310
+ *
311
+ * @returns {IncrementResponse} - The number of hits and reset time for that client.
312
+ *
313
+ * @public
314
+ */
315
+ increment(key: string): Promise<IncrementResponse>;
316
+ /**
317
+ * Method to decrement a client's hit counter.
318
+ *
319
+ * @param key {string} - The identifier for a client.
320
+ *
321
+ * @public
322
+ */
323
+ decrement(key: string): Promise<void>;
324
+ /**
325
+ * Method to reset a client's hit counter.
326
+ *
327
+ * @param key {string} - The identifier for a client.
328
+ *
329
+ * @public
330
+ */
331
+ resetKey(key: string): Promise<void>;
332
+ /**
333
+ * Method to reset everyone's hit counter.
334
+ *
335
+ * @public
336
+ */
337
+ resetAll(): Promise<void>;
338
+ }
339
+
340
+ export {
341
+ rateLimit as default,
342
+ };
247
343
 
248
344
  export {};
package/dist/index.mjs CHANGED
@@ -70,9 +70,9 @@ var promisifyStore = (passedStore) => {
70
70
  return new PromisifiedStore();
71
71
  };
72
72
  var parseOptions = (passedOptions) => {
73
- const options = {
73
+ const notUndefinedOptions = omitUndefinedOptions(passedOptions);
74
+ const config = {
74
75
  windowMs: 60 * 1e3,
75
- store: new MemoryStore(),
76
76
  max: 5,
77
77
  message: "Too many requests, please try again later.",
78
78
  statusCode: 429,
@@ -90,17 +90,17 @@ var parseOptions = (passedOptions) => {
90
90
  return request.ip;
91
91
  },
92
92
  handler: (_request, response, _next, _optionsUsed) => {
93
- response.status(options.statusCode).send(options.message);
93
+ response.status(config.statusCode).send(config.message);
94
94
  },
95
95
  onLimitReached: (_request, _response, _optionsUsed) => {
96
96
  },
97
- ...passedOptions
97
+ ...notUndefinedOptions,
98
+ store: promisifyStore(notUndefinedOptions.store ?? new MemoryStore())
98
99
  };
99
- if (typeof options.store.incr !== "function" && typeof options.store.increment !== "function" || typeof options.store.decrement !== "function" || typeof options.store.resetKey !== "function" || typeof options.store.resetAll !== "undefined" && typeof options.store.resetAll !== "function" || typeof options.store.init !== "undefined" && typeof options.store.init !== "function") {
100
+ if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
100
101
  throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
101
102
  }
102
- options.store = promisifyStore(options.store);
103
- return options;
103
+ return config;
104
104
  };
105
105
  var handleAsyncErrors = (fn) => async (request, response, next) => {
106
106
  try {
@@ -189,10 +189,19 @@ var rateLimit = (passedOptions) => {
189
189
  middleware.resetKey = options.store.resetKey.bind(options.store);
190
190
  return middleware;
191
191
  };
192
+ var omitUndefinedOptions = (passedOptions) => {
193
+ const omittedOptions = {};
194
+ for (const k of Object.keys(passedOptions)) {
195
+ const key = k;
196
+ if (passedOptions[key] !== void 0) {
197
+ omittedOptions[key] = passedOptions[key];
198
+ }
199
+ }
200
+ return omittedOptions;
201
+ };
192
202
  var lib_default = rateLimit;
193
-
194
- // source/index.ts
195
- var source_default = lib_default;
196
203
  export {
197
- source_default as default
204
+ MemoryStore,
205
+ lib_default as default,
206
+ lib_default as rateLimit
198
207
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "6.0.4",
3
+ "version": "6.2.1",
4
4
  "description": "Basic IP rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset.",
5
5
  "author": {
6
6
  "name": "Nathan Friedly",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "scripts": {
52
52
  "clean": "del-cli dist/ coverage/ *.log *.tmp *.bak *.tgz",
53
- "build:cjs": "esbuild --bundle --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit;\" source/index.ts",
53
+ "build:cjs": "esbuild --bundle --format=cjs --outfile=dist/index.cjs --footer:js=\"module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;\" source/index.ts",
54
54
  "build:esm": "esbuild --bundle --format=esm --outfile=dist/index.mjs source/index.ts",
55
55
  "build:types": "dts-bundle-generator --out-file=dist/index.d.ts source/index.ts",
56
56
  "compile": "run-s clean build:*",
@@ -61,7 +61,7 @@
61
61
  "autofix:rest": "prettier --ignore-path .gitignore --ignore-unknown --write .",
62
62
  "autofix": "run-s autofix:*",
63
63
  "test:lib": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
64
- "test:ext": "npm pack && cd test/external/ && bash run-all-tests",
64
+ "test:ext": "cd test/external/ && bash run-all-tests",
65
65
  "test": "run-s lint test:*",
66
66
  "pre-commit": "lint-staged",
67
67
  "prepare": "run-s compile && husky install config/husky"
@@ -70,24 +70,24 @@
70
70
  "express": "^4"
71
71
  },
72
72
  "devDependencies": {
73
- "@jest/globals": "^27.4.2",
73
+ "@jest/globals": "^27.4.6",
74
74
  "@types/express": "^4.17.13",
75
- "@types/jest": "^27.0.3",
76
- "@types/node": "^16.11.17",
75
+ "@types/jest": "^27.4.0",
76
+ "@types/node": "^16.11.21",
77
77
  "@types/supertest": "^2.0.11",
78
78
  "cross-env": "^7.0.3",
79
79
  "del-cli": "^4.0.1",
80
- "dts-bundle-generator": "^6.2.0",
81
- "esbuild": "^0.14.8",
80
+ "dts-bundle-generator": "^6.4.0",
81
+ "esbuild": "^0.14.12",
82
82
  "express": "^4.17.1",
83
83
  "husky": "^7.0.4",
84
- "jest": "^27.4.3",
85
- "lint-staged": "^12.1.2",
84
+ "jest": "^27.4.7",
85
+ "lint-staged": "^12.2.2",
86
86
  "npm-run-all": "^4.1.5",
87
- "supertest": "^6.1.6",
88
- "ts-jest": "^27.1.1",
87
+ "supertest": "^6.2.2",
88
+ "ts-jest": "^27.1.3",
89
89
  "ts-node": "^10.4.0",
90
- "typescript": "^4.5.2",
90
+ "typescript": "^4.5.5",
91
91
  "xo": "^0.47.0"
92
92
  },
93
93
  "xo": {
package/readme.md CHANGED
@@ -22,9 +22,9 @@ This module was designed to only handle the basics and didn't even support
22
22
  external stores initially. These other options all are excellent pieces of
23
23
  software and may be more appropriate for some situations:
24
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)
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
28
 
29
29
  ## Installation
30
30
 
@@ -58,57 +58,19 @@ Javascript and Typescript projects.
58
58
 
59
59
  **This package requires you to use Node 14 or above.**
60
60
 
61
- #### Javascript
62
-
63
- Import it in a CommonJS project as follows:
64
-
65
- ```ts
66
- const rateLimit = require('express-rate-limit')
67
- ```
68
-
69
- Import it in a ESM project as follows:
70
-
71
- ```ts
72
- import rateLimit from 'express-rate-limit'
73
- ```
74
-
75
- #### Typescript
76
-
77
- If you are using this library in a Typescript project that outputs CommonJS (no
78
- `type: module` in `package.json` and `module: commonjs` in `tsconfig.json`), set
79
- `esModuleInterop` to `true` in the `compilerOptions` of your `tsconfig.json` and
80
- then import it as follows:
81
-
82
- ```ts
83
- import rateLimit from 'express-rate-limit'
84
- ```
85
-
86
- If you cannot set `esModuleInterop` to true, import it as follows instead:
61
+ Import it in a CommonJS project (`type: commonjs` or no `type` field in
62
+ `package.json`) as follows:
87
63
 
88
64
  ```ts
89
65
  const rateLimit = require('express-rate-limit')
90
66
  ```
91
67
 
92
- And use the following to import any types if you need to:
93
-
94
- ```ts
95
- import { Store, IncrementResponse, ... } from 'express-rate-limit'
96
- ```
97
-
98
- If you are using this library in a Typescript project that outputs ESM
99
- (`type: module` in `package.json` and `module: esnext` in `tsconfig.json`),
100
- import it as follows:
68
+ Import it in a ESM project (`type: module` in `package.json`) as follows:
101
69
 
102
70
  ```ts
103
71
  import rateLimit from 'express-rate-limit'
104
72
  ```
105
73
 
106
- And use the following to import any types if you need to:
107
-
108
- ```ts
109
- import rateLimit, { Store, IncrementResponse, ... } from 'express-rate-limit'
110
- ```
111
-
112
74
  ### Examples
113
75
 
114
76
  To use it in an API-only server where the rate-limiter should be applied to all
@@ -177,8 +139,7 @@ app.post('/create-account', createAccountLimiter, (request, response) => {
177
139
  To use a custom store:
178
140
 
179
141
  ```ts
180
- import rateLimit from 'express-rate-limit'
181
- import MemoryStore from 'express-rate-limit/memory-store.js'
142
+ import rateLimit, { MemoryStore } from 'express-rate-limit'
182
143
 
183
144
  const apiLimiter = rateLimit({
184
145
  windowMs: 15 * 60 * 1000, // 15 minutes
@@ -227,64 +188,55 @@ it does.
227
188
  For more information about the `trust proxy` setting, take a look at the
228
189
  [official Express documentation](https://expressjs.com/en/guide/behind-proxies.html).
229
190
 
230
- ## Request API
231
-
232
- A `request.rateLimit` property is added to all requests with the `limit`,
233
- `current`, and `remaining` number of requests and, if the store provides it, a
234
- `resetTime` Date object. These may be used in your application code to take
235
- additional actions or inform the user of their status.
236
-
237
- The property name can be configured with the configuration option
238
- `requestPropertyName`
239
-
240
- ## Configuration options
191
+ ## Configuration
241
192
 
242
193
  ### `windowMs`
243
194
 
195
+ > `number`
196
+
244
197
  Time frame for which requests are checked/remembered. Also used in the
245
198
  `Retry-After` header when the limit is reached.
246
199
 
247
- Note: with non-default stores, you may need to configure this value twice, once
248
- here and once on the store. In some cases the units also differ (e.g. seconds vs
249
- miliseconds)
200
+ Note: with stores that do not implement the `init` function (see the table in
201
+ the [`stores` section below](#stores)), you may need to configure this value
202
+ twice, once here and once on the store. In some cases the units also differ
203
+ (e.g. seconds vs miliseconds).
250
204
 
251
205
  Defaults to `60000` ms (= 1 minute).
252
206
 
253
207
  ### `max`
254
208
 
255
- Max number of connections during `windowMs` milliseconds before sending a 429
256
- response.
209
+ > `number | function`
257
210
 
258
- May be a number, or a function that returns a number or a promise. If `max` is a
259
- function, it will be called with `request` and `response` params.
211
+ The maximum number of connections to allow during the `window` before rate
212
+ limiting the client.
260
213
 
261
- Defaults to `5`. Set to `0` to disable.
214
+ Can be the limit itself as a number or a (sync/async) function that accepts the
215
+ Express `request` and `response` objects and then returns a number.
262
216
 
263
- Example of using a function:
217
+ Defaults to `5`. Set it to `0` to disable the rate limiter.
264
218
 
265
- ```ts
266
- import rateLimit from 'express-rate-limit'
219
+ An example of using a function:
267
220
 
268
- const isPremium = (request) => {
221
+ ```ts
222
+ const isPremium = async (user) => {
269
223
  // ...
270
224
  }
271
225
 
272
226
  const limiter = rateLimit({
273
- // `max` could also be an async function or return a promise
274
- max: (request, response) => {
275
- if (isPremium(request)) return 10
227
+ // ...
228
+ max: async (request, response) => {
229
+ if (await isPremium(request.user)) return 10
276
230
  else return 5
277
231
  },
278
- // ...
279
232
  })
280
-
281
- // Apply the rate limiting middleware to all requests
282
- app.use(limiter)
283
233
  ```
284
234
 
285
235
  ### `message`
286
236
 
287
- Error message sent to user when `max` is exceeded.
237
+ > `any`
238
+
239
+ The response body to send back when a client is rate limited.
288
240
 
289
241
  May be a `string`, JSON object, or any other value that Express's
290
242
  [response.send](https://expressjs.com/en/4x/api.html#response.send) method
@@ -294,240 +246,208 @@ Defaults to `'Too many requests, please try again later.'`
294
246
 
295
247
  ### `statusCode`
296
248
 
297
- HTTP status code returned when `max` is exceeded.
249
+ > `number`
250
+
251
+ The HTTP status code to send back when a client is rate limited.
298
252
 
299
- Defaults to `429`.
253
+ Defaults to `429` (HTTP 429 Too Many Requests - RFC 6585).
300
254
 
301
255
  ### `legacyHeaders`
302
256
 
303
- Enable headers for request limit (`X-RateLimit-Limit`) and current usage
304
- (`X-RateLimit-Remaining`) on all responses and time to wait before retrying
305
- (`Retry-After`) when `max` is exceeded.
257
+ > `boolean`
306
258
 
307
- Defaults to `true`.
259
+ Whether to send the legacy rate limit headers for the limit
260
+ (`X-RateLimit-Limit`), current usage (`X-RateLimit-Remaining`) and reset time
261
+ (if the store provides it) (`X-RateLimit-Reset`) on all responses. If set to
262
+ `true`, the middleware also sends the `Retry-After` header on all blocked
263
+ requests.
264
+
265
+ Defaults to `true` (for backward compatibility).
308
266
 
309
267
  > Renamed in `6.x` from `headers` to `legacyHeaders`.
310
268
 
311
269
  ### `standardHeaders`
312
270
 
313
- Enable headers conforming to the
314
- [ratelimit standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers/blob/main/draft-ietf-httpapi-ratelimit-headers.md)
315
- adopted by the IETF: `RateLimit-Limit`, `RateLimit-Remaining`, and, if the store
316
- supports it, `RateLimit-Reset`. May be used in conjunction with, or instead of
317
- the `legacyHeaders` option.
271
+ > `boolean`
318
272
 
319
- This setting also enables the `Retry-After` header when `max` is exceeded.
273
+ Whether to enable support for headers conforming to the
274
+ [ratelimit standardization draft](https://github.com/ietf-wg-httpapi/ratelimit-headers/blob/main/draft-ietf-httpapi-ratelimit-headers.md)
275
+ adopted by the IETF (`RateLimit-Limit`, `RateLimit-Remaining`, and, if the store
276
+ supports it, `RateLimit-Reset`). If set to `true`, the middleware also sends the
277
+ `Retry-After` header on all blocked requests. May be used in conjunction with,
278
+ or instead of the `legacyHeaders` option.
320
279
 
321
- Defaults to `false` (for backward compatibility), but recommended to use.
280
+ Defaults to `false` (for backward compatibility, but its use is recommended).
322
281
 
323
282
  > Renamed in `6.x` from `draft_polli_ratelimit_headers` to `standardHeaders`.
324
283
 
325
- ### `keyGenerator`
284
+ ### `requestPropertyName`
326
285
 
327
- Function used to generate keys.
286
+ > `string`
328
287
 
329
- Defaults to `request.ip`, similar to this:
288
+ The name of the property on the Express `request` object to store the rate limit
289
+ info.
330
290
 
331
- ```ts
332
- const keyGenerator = (request /*, response*/) => request.ip
333
- ```
291
+ Defaults to `'rateLimit'`.
334
292
 
335
- ### `handler`
293
+ ### `skipFailedRequests`
336
294
 
337
- The function to handle requests once the max limit is exceeded. It receives the
338
- `request` and the `response` objects. The `next` param is available if you need
339
- to pass to the next middleware/route. Finally, the `options` param has all of
340
- the options that originally passed in when creating the current limiter and the
341
- default values for other options.
295
+ > `boolean`
342
296
 
343
- The `request.rateLimit` object has `limit`, `current`, and `remaining` number of
344
- requests and, if the store provides it, a `resetTime` Date object.
297
+ When set to `true`, failed requests won't be counted. Request considered failed
298
+ when the `requestWasSuccessful` option returns `false`. By default, this means
299
+ requests fail when:
345
300
 
346
- Defaults to:
301
+ - the response status >= 400
302
+ - the request was cancelled before last chunk of data was sent (response `close`
303
+ event triggered)
304
+ - the response `error` event was triggered by response
305
+
306
+ (Technically they are counted and then un-counted, so a large number of slow
307
+ requests all at once could still trigger a rate-limit. This may be fixed in a
308
+ future release. PRs welcome!)
309
+
310
+ Defaults to `false`.
311
+
312
+ ### `skipSuccessfulRequests`
313
+
314
+ > `boolean`
315
+
316
+ If `true`, the library will (by default) skip all requests that are considered
317
+ 'failed' by the `requestWasSuccessful` function. By default, this means requests
318
+ succeed when the response status code < 400.
319
+
320
+ (Technically they are counted and then un-counted, so a large number of slow
321
+ requests all at once could still trigger a rate-limit. This may be fixed in a
322
+ future release. PRs welcome!)
323
+
324
+ Defaults to `false`.
325
+
326
+ ### `keyGenerator`
327
+
328
+ > `function`
329
+
330
+ Method to generate custom identifiers for clients.
331
+
332
+ Should be a (sync/async) function that accepts the Express `request` and
333
+ `response` objects and then returns a string.
334
+
335
+ By default, the client's IP address is used:
347
336
 
348
337
  ```ts
349
- const handler = (request, response, next, options) => {
350
- response.status(options.statusCode).send(options.message)
351
- }
338
+ const limiter = rateLimit({
339
+ // ...
340
+ keyGenerator: (request, response) => request.ip,
341
+ })
352
342
  ```
353
343
 
354
- ### `requestWasSuccessful`
344
+ ### `handler`
345
+
346
+ > `function`
355
347
 
356
- Function that is called when `skipFailedRequests` and/or
357
- `skipSuccessfulRequests` are set to `true`. May be overridden if, for example, a
358
- service sends out a 200 status code on errors.
348
+ Express request handler that sends back a response when a client is
349
+ rate-limited.
359
350
 
360
- Defaults to
351
+ By default, sends back the `statusCode` and `message` set via the options:
361
352
 
362
353
  ```ts
363
- const requestWasSuccessful = (request, response) => response.statusCode < 400
354
+ const limiter = rateLimit({
355
+ // ...
356
+ handler: (request, response, next, options) =>
357
+ response.status(options.statusCode).send(options.message),
358
+ })
364
359
  ```
365
360
 
366
- ### `skipFailedRequests`
361
+ ### `onLimitReached`
367
362
 
368
- When set to `true`, failed requests won't be counted. Request considered failed
369
- when:
363
+ > `function`
370
364
 
371
- - response status >= 400
372
- - requests that were cancelled before last chunk of data was sent (response
373
- `close` event triggered)
374
- - response `error` event was triggered by response
365
+ A (sync/async) function that accepts the Express `request` and `response`
366
+ objects that is called when a client has reached their rate limit, and will be
367
+ rate limited on their next request.
375
368
 
376
- (Technically they are counted and then un-counted, so a large number of slow
377
- requests all at once could still trigger a rate-limit. This may be fixed in a
378
- future release.)
369
+ This method was
370
+ [deprecated in v6](https://github.com/nfriedly/express-rate-limit/releases/v6.0.0) -
371
+ Please use a custom `handler` that checks the number of hits instead.
379
372
 
380
- Defaults to `false`.
373
+ ### `skip`
381
374
 
382
- ### `skipSuccessfulRequests`
375
+ > `function`
383
376
 
384
- When set to `true` successful requests (response status < 400) won't be counted.
385
- (Technically they are counted and then un-counted, so a large number of slow
386
- requests all at once could still trigger a rate-limit. This may be fixed in a
387
- future release.)
377
+ Function to determine whether or not this request counts towards a client's
378
+ quota. Should be a (sync/async) function that accepts the Express `request` and
379
+ `response` objects and then returns `true` or `false`.
388
380
 
389
- Defaults to `false`.
381
+ Could also act as an allowlist for certain keys:
390
382
 
391
- ### `skip`
383
+ ```ts
384
+ const allowlist = ['192.168.0.56', '192.168.0.21']
392
385
 
393
- Function used to skip (whitelist) requests. Returning `true`, or a promise that
394
- resolves with `true`, from the function will skip limiting for that request.
386
+ const limiter = rateLimit({
387
+ // ...
388
+ skip: (request, response) => allowlist.includes(request.ip),
389
+ })
390
+ ```
395
391
 
396
- Defaults to always `false` (count all requests):
392
+ By default, it skips no requests:
397
393
 
398
394
  ```ts
399
- const skip = (/*request, response*/) => false
395
+ const limiter = rateLimit({
396
+ // ...
397
+ skip: (request, response) => false,
398
+ })
400
399
  ```
401
400
 
402
- ### `requestPropertyName`
401
+ ### `requestWasSuccessful`
402
+
403
+ > `function`
403
404
 
404
- The name of the property that contains the rate limit information to add to the
405
- `request` object.
405
+ Method to determine whether or not the request counts as 'succesful'. Used when
406
+ either `skipSuccessfulRequests` or `skipFailedRequests` is set to true. Should
407
+ be a (sync/async) function that accepts the Express `request` and `response`
408
+ objects and then returns `true` or `false`.
406
409
 
407
- Defaults to `rateLimit`.
410
+ By default, requests with a response status code less than 400 are considered
411
+ successful:
412
+
413
+ ```ts
414
+ const limiter = rateLimit({
415
+ // ...
416
+ requestWasSuccessful: (request, response) => response.statusCode < 400,
417
+ })
418
+ ```
408
419
 
409
420
  ### `store`
410
421
 
411
- The storage to use when persisting rate limit attempts.
422
+ > `Store`
412
423
 
413
- By default, the [memory store](source/memory-store.ts) is used.
424
+ The `Store` to use to store the hit count for each client.
414
425
 
415
- Available data stores are:
426
+ By default, the [`memory-store`](source/memory-store.ts) is used.
416
427
 
417
- - [memory-store](source/memory-store.ts): _(default)_ Simple in-memory option.
418
- Does not share state when app has multiple processes or servers.
419
- - [rate-limit-redis](https://npmjs.com/package/rate-limit-redis): A
420
- [Redis](http://redis.io/)-backed store, more suitable for large or demanding
421
- deployments.
422
- - [rate-limit-memcached](https://npmjs.org/package/rate-limit-memcached): A
423
- [Memcached](https://memcached.org/)-backed store.
424
- - [rate-limit-mongo](https://www.npmjs.com/package/rate-limit-mongo): A
425
- [MongoDB](https://www.mongodb.com/)-backed store.
426
- - [precise-memory-rate-limit](https://www.npmjs.com/package/precise-memory-rate-limit) -
427
- A memory store similar to the built-in one, except that it stores a distinct
428
- timestamp for each IP rather than bucketing them together.
428
+ Here is a list of external stores:
429
429
 
430
- You may also create your own store. It must implement the `Store` interface as
431
- follows:
430
+ | Name | Description | Legacy/Modern |
431
+ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------- |
432
+ | [`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 |
433
+ | [`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 |
434
+ | [`rate-limit-memcached`](https://npmjs.org/package/rate-limit-memcached) | A [Memcached](https://memcached.org/)-backed store. | Legacy |
435
+ | [`rate-limit-mongo`](https://www.npmjs.com/package/rate-limit-mongo) | A [MongoDB](https://www.mongodb.com/)-backed store. | Legacy |
436
+ | [`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. | Legacy |
432
437
 
433
- ```ts
434
- import rateLimit, {
435
- Store,
436
- Options,
437
- IncrementResponse,
438
- } from 'express-rate-limit'
439
-
440
- /**
441
- * A {@link Store} that stores the hit count for each client.
442
- *
443
- * @public
444
- */
445
- class SomeStore implements Store {
446
- /**
447
- * Some store-specific parameter.
448
- */
449
- customParam!: string
450
- /**
451
- * The duration of time before which all hit counts are reset (in milliseconds).
452
- */
453
- windowMs!: number
454
-
455
- /**
456
- * @constructor for {@link SomeStore}. Only required if the user needs to pass
457
- * some store specific parameters. For example, in a Mongo Store, the user will
458
- * need to pass the URI, username and password for the Mongo database.
459
- *
460
- * @param customParam {string} - Some store-specific parameter.
461
- */
462
- constructor(customParam: string) {
463
- this.customParam = customParam
464
- }
465
-
466
- /**
467
- * Method that actually initializes the store. Must be synchronous.
468
- *
469
- * @param options {Options} - The options used to setup the middleware.
470
- *
471
- * @public
472
- */
473
- init(options: Options): void {
474
- this.windowMs = options.windowMs
475
-
476
- // ...
477
- }
478
-
479
- /**
480
- * Method to increment a client's hit counter.
481
- *
482
- * @param key {string} - The identifier for a client
483
- *
484
- * @returns {IncrementResponse} - The number of hits and reset time for that client
485
- *
486
- * @public
487
- */
488
- async increment(key: string): Promise<IncrementResponse> {
489
- // ...
490
-
491
- return {
492
- totalHits,
493
- resetTime,
494
- }
495
- }
496
-
497
- /**
498
- * Method to decrement a client's hit counter.
499
- *
500
- * @param key {string} - The identifier for a client
501
- *
502
- * @public
503
- */
504
- async decrement(key: string): Promise<void> {
505
- // ...
506
- }
507
-
508
- /**
509
- * Method to reset a client's hit counter.
510
- *
511
- * @param key {string} - The identifier for a client
512
- *
513
- * @public
514
- */
515
- async resetKey(key: string): Promise<void> {
516
- // ...
517
- }
518
-
519
- /**
520
- * Method to reset everyone's hit counter.
521
- *
522
- * @public
523
- */
524
- async resetAll(): Promise<void> {
525
- // ...
526
- }
527
- }
438
+ Take a look at
439
+ [this guide](https://github.com/nfriedly/express-rate-limit/wiki/Creating-Your-Own-Store)
440
+ if you wish to create your own store.
528
441
 
529
- export default SomeStore
530
- ```
442
+ ## Request API
443
+
444
+ A `request.rateLimit` property is added to all requests with the `limit`,
445
+ `current`, and `remaining` number of requests and, if the store provides it, a
446
+ `resetTime` Date object. These may be used in your application code to take
447
+ additional actions or inform the user of their status.
448
+
449
+ The property name can be configured with the configuration option
450
+ `requestPropertyName`.
531
451
 
532
452
  ## Instance API
533
453
 
package/tsconfig.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
+ "include": ["source/"],
3
+ "exclude": ["node_modules/"],
2
4
  "compilerOptions": {
3
5
  "declaration": true,
4
-
5
6
  "strict": true,
6
7
  "noUnusedLocals": true,
7
8
  "noImplicitReturns": true,
8
9
  "noFallthroughCasesInSwitch": true,
9
-
10
- "moduleResolution": "node",
11
- "esModuleInterop": true
12
- },
13
- "include": ["./source/**/*.ts"],
14
- "exclude": ["./node_modules"]
10
+ "esModuleInterop": true,
11
+ "moduleResolution": "node"
12
+ }
15
13
  }