express-rate-limit 8.2.1 → 8.5.2

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/dist/index.cjs CHANGED
@@ -31,8 +31,13 @@ module.exports = __toCommonJS(index_exports);
31
31
  var import_node_net = require("node:net");
32
32
  var import_ip_address = require("ip-address");
33
33
  function ipKeyGenerator(ip, ipv6Subnet = 56) {
34
- if (ipv6Subnet && (0, import_node_net.isIPv6)(ip)) {
35
- return `${new import_ip_address.Address6(`${ip}/${ipv6Subnet}`).startAddress().correctForm()}/${ipv6Subnet}`;
34
+ if ((0, import_node_net.isIPv6)(ip)) {
35
+ const address = new import_ip_address.Address6(ip);
36
+ if (address.is4()) return address.to4().correctForm();
37
+ if (ipv6Subnet) {
38
+ const subnet = new import_ip_address.Address6(`${ip}/${ipv6Subnet}`);
39
+ return subnet.networkForm();
40
+ }
36
41
  }
37
42
  return ip;
38
43
  }
@@ -196,6 +201,16 @@ var MemoryStore = class {
196
201
  // source/rate-limit.ts
197
202
  var import_node_net3 = require("node:net");
198
203
 
204
+ // source/console-logger.ts
205
+ var ConsoleLogger = {
206
+ warn(...args) {
207
+ console.warn(...args.reverse());
208
+ },
209
+ error(...args) {
210
+ console.error(...args.reverse());
211
+ }
212
+ };
213
+
199
214
  // source/headers.ts
200
215
  var import_node_buffer = require("node:buffer");
201
216
  var import_node_crypto = require("node:crypto");
@@ -543,7 +558,8 @@ var validations = {
543
558
  validate: true,
544
559
  headers: true,
545
560
  max: true,
546
- passOnStoreError: true
561
+ passOnStoreError: true,
562
+ logger: true
547
563
  };
548
564
  const validOptions = Object.keys(optionsMap).concat(
549
565
  "draft_polli_ratelimit_headers",
@@ -656,7 +672,15 @@ var validations = {
656
672
  }
657
673
  }
658
674
  };
659
- var getValidations = (_enabled) => {
675
+ function validateLogger(logger) {
676
+ if (typeof logger !== "object" || typeof logger.error !== "function" || typeof logger.warn !== "function") {
677
+ throw new TypeError(
678
+ "Provided logger does not implement the Logger interface"
679
+ );
680
+ }
681
+ }
682
+ var getValidations = (_enabled, logger) => {
683
+ validateLogger(logger);
660
684
  let enabled;
661
685
  if (typeof _enabled === "boolean") {
662
686
  enabled = {
@@ -682,8 +706,8 @@ var getValidations = (_enabled) => {
682
706
  args
683
707
  );
684
708
  } catch (error) {
685
- if (error instanceof ChangeWarning) console.warn(error);
686
- else console.error(error);
709
+ if (error instanceof ChangeWarning) logger.warn(error);
710
+ else logger.error(error);
687
711
  }
688
712
  };
689
713
  }
@@ -736,7 +760,11 @@ var getOptionsFromConfig = (config) => {
736
760
  };
737
761
  var parseOptions = (passedOptions) => {
738
762
  const notUndefinedOptions = omitUndefinedProperties(passedOptions);
739
- const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
763
+ const logger = passedOptions.logger ?? ConsoleLogger;
764
+ const validations2 = getValidations(
765
+ notUndefinedOptions?.validate ?? true,
766
+ logger
767
+ );
740
768
  validations2.validationsConfig();
741
769
  validations2.knownOptions(passedOptions);
742
770
  validations2.draftPolliHeaders(
@@ -811,7 +839,8 @@ var parseOptions = (passedOptions) => {
811
839
  notUndefinedOptions.store ?? new MemoryStore(validations2)
812
840
  ),
813
841
  // Print an error to the console if a few known misconfigurations are detected.
814
- validations: validations2
842
+ validations: validations2,
843
+ logger
815
844
  };
816
845
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
817
846
  throw new TypeError(
@@ -832,9 +861,29 @@ var rateLimit = (passedOptions) => {
832
861
  const options = getOptionsFromConfig(config);
833
862
  config.validations.creationStack(config.store);
834
863
  config.validations.unsharedStore(config.store);
835
- if (typeof config.store.init === "function") config.store.init(options);
864
+ if (typeof config.store.init === "function") {
865
+ try {
866
+ const storeInit = config.store.init(options);
867
+ if (storeInit instanceof Promise) {
868
+ storeInit.catch(
869
+ (error) => config.logger.error(
870
+ error,
871
+ "express-rate-limit: async error during store initialization."
872
+ )
873
+ );
874
+ }
875
+ } catch (error) {
876
+ config.logger.error(
877
+ error,
878
+ "express-rate-limit: error during store initialization."
879
+ );
880
+ }
881
+ }
836
882
  const middleware = handleAsyncErrors(
837
883
  async (request, response, next) => {
884
+ const closePromise = config.skipFailedRequests && new Promise((resolve) => response.once("close", resolve));
885
+ const finishPromise = (config.skipFailedRequests || config.skipSuccessfulRequests) && new Promise((resolve) => response.once("finish", resolve));
886
+ const errorPromise = config.skipFailedRequests && new Promise((resolve) => response.once("error", resolve));
838
887
  const skip = await config.skip(request, response);
839
888
  if (skip) {
840
889
  next();
@@ -850,9 +899,9 @@ var rateLimit = (passedOptions) => {
850
899
  resetTime = incrementResult.resetTime;
851
900
  } catch (error) {
852
901
  if (config.passOnStoreError) {
853
- console.error(
854
- "express-rate-limit: error from store, allowing request without rate-limiting.",
855
- error
902
+ config.logger.error(
903
+ error,
904
+ "express-rate-limit: error from store, allowing request without rate-limiting."
856
905
  );
857
906
  next();
858
907
  return;
@@ -913,22 +962,30 @@ var rateLimit = (passedOptions) => {
913
962
  }
914
963
  };
915
964
  if (config.skipFailedRequests) {
916
- response.on("finish", async () => {
917
- if (!await config.requestWasSuccessful(request, response))
965
+ if (finishPromise) {
966
+ void finishPromise.then(async () => {
967
+ if (!await config.requestWasSuccessful(request, response))
968
+ await decrementKey();
969
+ });
970
+ }
971
+ if (closePromise) {
972
+ void closePromise.then(async () => {
973
+ if (!response.writableEnded) await decrementKey();
974
+ });
975
+ }
976
+ if (errorPromise) {
977
+ void errorPromise.then(async () => {
918
978
  await decrementKey();
919
- });
920
- response.on("close", async () => {
921
- if (!response.writableEnded) await decrementKey();
922
- });
923
- response.on("error", async () => {
924
- await decrementKey();
925
- });
979
+ });
980
+ }
926
981
  }
927
982
  if (config.skipSuccessfulRequests) {
928
- response.on("finish", async () => {
929
- if (await config.requestWasSuccessful(request, response))
930
- await decrementKey();
931
- });
983
+ if (finishPromise) {
984
+ void finishPromise.then(async () => {
985
+ if (await config.requestWasSuccessful(request, response))
986
+ await decrementKey();
987
+ });
988
+ }
932
989
  }
933
990
  }
934
991
  config.validations.disable();
package/dist/index.d.cts CHANGED
@@ -159,6 +159,26 @@ declare const validations: {
159
159
  windowMs(windowMs: number): void;
160
160
  };
161
161
  export type Validations = typeof validations;
162
+ /**
163
+ * Basic logging function
164
+ *
165
+ * @param error {unknown} - The error to log
166
+ * @param message {string | undefined} - Additional details about the error
167
+ */
168
+ export type LoggerFn = (error: unknown, message?: string) => void;
169
+ /**
170
+ * Minimal interface for logging warnings and errors
171
+ */
172
+ export type Logger = {
173
+ /**
174
+ * Function to log an error
175
+ */
176
+ error: LoggerFn;
177
+ /**
178
+ * Function to log a warning
179
+ */
180
+ warn: LoggerFn;
181
+ };
162
182
  /**
163
183
  * Callback that fires when a client's hit counter is incremented.
164
184
  *
@@ -265,9 +285,15 @@ export type Store = {
265
285
  * Method that initializes the store, and has access to the options passed to
266
286
  * the middleware too.
267
287
  *
288
+ * Called once during initialization.
289
+ *
290
+ * Errors / promise rejections will be caught and logged.
291
+ *
292
+ * Note that the result is not awaited - other store methods (such as increment) may be called before init returns and/or after it throws/rejects.
293
+ *
268
294
  * @param options {Options} - The options used to setup the middleware.
269
295
  */
270
- init?: (options: Options) => void;
296
+ init?: (options: Options) => void | Promise<void>;
271
297
  /**
272
298
  * Method to fetch a client's hit count and reset time.
273
299
  *
@@ -477,6 +503,10 @@ export type Options = {
477
503
  * If the Store generates an error, allow the request to pass.
478
504
  */
479
505
  passOnStoreError: boolean;
506
+ /**
507
+ * The logger to use to log errors. If absent, logs to the console.
508
+ */
509
+ logger: Logger;
480
510
  };
481
511
  /**
482
512
  * The extended request object that includes information about the client's
package/dist/index.d.mts CHANGED
@@ -159,6 +159,26 @@ declare const validations: {
159
159
  windowMs(windowMs: number): void;
160
160
  };
161
161
  export type Validations = typeof validations;
162
+ /**
163
+ * Basic logging function
164
+ *
165
+ * @param error {unknown} - The error to log
166
+ * @param message {string | undefined} - Additional details about the error
167
+ */
168
+ export type LoggerFn = (error: unknown, message?: string) => void;
169
+ /**
170
+ * Minimal interface for logging warnings and errors
171
+ */
172
+ export type Logger = {
173
+ /**
174
+ * Function to log an error
175
+ */
176
+ error: LoggerFn;
177
+ /**
178
+ * Function to log a warning
179
+ */
180
+ warn: LoggerFn;
181
+ };
162
182
  /**
163
183
  * Callback that fires when a client's hit counter is incremented.
164
184
  *
@@ -265,9 +285,15 @@ export type Store = {
265
285
  * Method that initializes the store, and has access to the options passed to
266
286
  * the middleware too.
267
287
  *
288
+ * Called once during initialization.
289
+ *
290
+ * Errors / promise rejections will be caught and logged.
291
+ *
292
+ * Note that the result is not awaited - other store methods (such as increment) may be called before init returns and/or after it throws/rejects.
293
+ *
268
294
  * @param options {Options} - The options used to setup the middleware.
269
295
  */
270
- init?: (options: Options) => void;
296
+ init?: (options: Options) => void | Promise<void>;
271
297
  /**
272
298
  * Method to fetch a client's hit count and reset time.
273
299
  *
@@ -477,6 +503,10 @@ export type Options = {
477
503
  * If the Store generates an error, allow the request to pass.
478
504
  */
479
505
  passOnStoreError: boolean;
506
+ /**
507
+ * The logger to use to log errors. If absent, logs to the console.
508
+ */
509
+ logger: Logger;
480
510
  };
481
511
  /**
482
512
  * The extended request object that includes information about the client's
package/dist/index.d.ts CHANGED
@@ -159,6 +159,26 @@ declare const validations: {
159
159
  windowMs(windowMs: number): void;
160
160
  };
161
161
  export type Validations = typeof validations;
162
+ /**
163
+ * Basic logging function
164
+ *
165
+ * @param error {unknown} - The error to log
166
+ * @param message {string | undefined} - Additional details about the error
167
+ */
168
+ export type LoggerFn = (error: unknown, message?: string) => void;
169
+ /**
170
+ * Minimal interface for logging warnings and errors
171
+ */
172
+ export type Logger = {
173
+ /**
174
+ * Function to log an error
175
+ */
176
+ error: LoggerFn;
177
+ /**
178
+ * Function to log a warning
179
+ */
180
+ warn: LoggerFn;
181
+ };
162
182
  /**
163
183
  * Callback that fires when a client's hit counter is incremented.
164
184
  *
@@ -265,9 +285,15 @@ export type Store = {
265
285
  * Method that initializes the store, and has access to the options passed to
266
286
  * the middleware too.
267
287
  *
288
+ * Called once during initialization.
289
+ *
290
+ * Errors / promise rejections will be caught and logged.
291
+ *
292
+ * Note that the result is not awaited - other store methods (such as increment) may be called before init returns and/or after it throws/rejects.
293
+ *
268
294
  * @param options {Options} - The options used to setup the middleware.
269
295
  */
270
- init?: (options: Options) => void;
296
+ init?: (options: Options) => void | Promise<void>;
271
297
  /**
272
298
  * Method to fetch a client's hit count and reset time.
273
299
  *
@@ -477,6 +503,10 @@ export type Options = {
477
503
  * If the Store generates an error, allow the request to pass.
478
504
  */
479
505
  passOnStoreError: boolean;
506
+ /**
507
+ * The logger to use to log errors. If absent, logs to the console.
508
+ */
509
+ logger: Logger;
480
510
  };
481
511
  /**
482
512
  * The extended request object that includes information about the client's
package/dist/index.mjs CHANGED
@@ -2,8 +2,13 @@
2
2
  import { isIPv6 } from "node:net";
3
3
  import { Address6 } from "ip-address";
4
4
  function ipKeyGenerator(ip, ipv6Subnet = 56) {
5
- if (ipv6Subnet && isIPv6(ip)) {
6
- return `${new Address6(`${ip}/${ipv6Subnet}`).startAddress().correctForm()}/${ipv6Subnet}`;
5
+ if (isIPv6(ip)) {
6
+ const address = new Address6(ip);
7
+ if (address.is4()) return address.to4().correctForm();
8
+ if (ipv6Subnet) {
9
+ const subnet = new Address6(`${ip}/${ipv6Subnet}`);
10
+ return subnet.networkForm();
11
+ }
7
12
  }
8
13
  return ip;
9
14
  }
@@ -167,6 +172,16 @@ var MemoryStore = class {
167
172
  // source/rate-limit.ts
168
173
  import { isIPv6 as isIPv62 } from "node:net";
169
174
 
175
+ // source/console-logger.ts
176
+ var ConsoleLogger = {
177
+ warn(...args) {
178
+ console.warn(...args.reverse());
179
+ },
180
+ error(...args) {
181
+ console.error(...args.reverse());
182
+ }
183
+ };
184
+
170
185
  // source/headers.ts
171
186
  import { Buffer } from "node:buffer";
172
187
  import { createHash } from "node:crypto";
@@ -514,7 +529,8 @@ var validations = {
514
529
  validate: true,
515
530
  headers: true,
516
531
  max: true,
517
- passOnStoreError: true
532
+ passOnStoreError: true,
533
+ logger: true
518
534
  };
519
535
  const validOptions = Object.keys(optionsMap).concat(
520
536
  "draft_polli_ratelimit_headers",
@@ -627,7 +643,15 @@ var validations = {
627
643
  }
628
644
  }
629
645
  };
630
- var getValidations = (_enabled) => {
646
+ function validateLogger(logger) {
647
+ if (typeof logger !== "object" || typeof logger.error !== "function" || typeof logger.warn !== "function") {
648
+ throw new TypeError(
649
+ "Provided logger does not implement the Logger interface"
650
+ );
651
+ }
652
+ }
653
+ var getValidations = (_enabled, logger) => {
654
+ validateLogger(logger);
631
655
  let enabled;
632
656
  if (typeof _enabled === "boolean") {
633
657
  enabled = {
@@ -653,8 +677,8 @@ var getValidations = (_enabled) => {
653
677
  args
654
678
  );
655
679
  } catch (error) {
656
- if (error instanceof ChangeWarning) console.warn(error);
657
- else console.error(error);
680
+ if (error instanceof ChangeWarning) logger.warn(error);
681
+ else logger.error(error);
658
682
  }
659
683
  };
660
684
  }
@@ -707,7 +731,11 @@ var getOptionsFromConfig = (config) => {
707
731
  };
708
732
  var parseOptions = (passedOptions) => {
709
733
  const notUndefinedOptions = omitUndefinedProperties(passedOptions);
710
- const validations2 = getValidations(notUndefinedOptions?.validate ?? true);
734
+ const logger = passedOptions.logger ?? ConsoleLogger;
735
+ const validations2 = getValidations(
736
+ notUndefinedOptions?.validate ?? true,
737
+ logger
738
+ );
711
739
  validations2.validationsConfig();
712
740
  validations2.knownOptions(passedOptions);
713
741
  validations2.draftPolliHeaders(
@@ -782,7 +810,8 @@ var parseOptions = (passedOptions) => {
782
810
  notUndefinedOptions.store ?? new MemoryStore(validations2)
783
811
  ),
784
812
  // Print an error to the console if a few known misconfigurations are detected.
785
- validations: validations2
813
+ validations: validations2,
814
+ logger
786
815
  };
787
816
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || config.store.resetAll !== void 0 && typeof config.store.resetAll !== "function" || config.store.init !== void 0 && typeof config.store.init !== "function") {
788
817
  throw new TypeError(
@@ -803,9 +832,29 @@ var rateLimit = (passedOptions) => {
803
832
  const options = getOptionsFromConfig(config);
804
833
  config.validations.creationStack(config.store);
805
834
  config.validations.unsharedStore(config.store);
806
- if (typeof config.store.init === "function") config.store.init(options);
835
+ if (typeof config.store.init === "function") {
836
+ try {
837
+ const storeInit = config.store.init(options);
838
+ if (storeInit instanceof Promise) {
839
+ storeInit.catch(
840
+ (error) => config.logger.error(
841
+ error,
842
+ "express-rate-limit: async error during store initialization."
843
+ )
844
+ );
845
+ }
846
+ } catch (error) {
847
+ config.logger.error(
848
+ error,
849
+ "express-rate-limit: error during store initialization."
850
+ );
851
+ }
852
+ }
807
853
  const middleware = handleAsyncErrors(
808
854
  async (request, response, next) => {
855
+ const closePromise = config.skipFailedRequests && new Promise((resolve) => response.once("close", resolve));
856
+ const finishPromise = (config.skipFailedRequests || config.skipSuccessfulRequests) && new Promise((resolve) => response.once("finish", resolve));
857
+ const errorPromise = config.skipFailedRequests && new Promise((resolve) => response.once("error", resolve));
809
858
  const skip = await config.skip(request, response);
810
859
  if (skip) {
811
860
  next();
@@ -821,9 +870,9 @@ var rateLimit = (passedOptions) => {
821
870
  resetTime = incrementResult.resetTime;
822
871
  } catch (error) {
823
872
  if (config.passOnStoreError) {
824
- console.error(
825
- "express-rate-limit: error from store, allowing request without rate-limiting.",
826
- error
873
+ config.logger.error(
874
+ error,
875
+ "express-rate-limit: error from store, allowing request without rate-limiting."
827
876
  );
828
877
  next();
829
878
  return;
@@ -884,22 +933,30 @@ var rateLimit = (passedOptions) => {
884
933
  }
885
934
  };
886
935
  if (config.skipFailedRequests) {
887
- response.on("finish", async () => {
888
- if (!await config.requestWasSuccessful(request, response))
936
+ if (finishPromise) {
937
+ void finishPromise.then(async () => {
938
+ if (!await config.requestWasSuccessful(request, response))
939
+ await decrementKey();
940
+ });
941
+ }
942
+ if (closePromise) {
943
+ void closePromise.then(async () => {
944
+ if (!response.writableEnded) await decrementKey();
945
+ });
946
+ }
947
+ if (errorPromise) {
948
+ void errorPromise.then(async () => {
889
949
  await decrementKey();
890
- });
891
- response.on("close", async () => {
892
- if (!response.writableEnded) await decrementKey();
893
- });
894
- response.on("error", async () => {
895
- await decrementKey();
896
- });
950
+ });
951
+ }
897
952
  }
898
953
  if (config.skipSuccessfulRequests) {
899
- response.on("finish", async () => {
900
- if (await config.requestWasSuccessful(request, response))
901
- await decrementKey();
902
- });
954
+ if (finishPromise) {
955
+ void finishPromise.then(async () => {
956
+ if (await config.requestWasSuccessful(request, response))
957
+ await decrementKey();
958
+ });
959
+ }
903
960
  }
904
961
  }
905
962
  config.validations.disable();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-rate-limit",
3
- "version": "8.2.1",
3
+ "version": "8.5.2",
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",
@@ -70,34 +70,38 @@
70
70
  "test:lib": "jest",
71
71
  "test:ext": "cd test/external/ && bash run-all-tests",
72
72
  "test": "run-s lint test:lib",
73
+ "format-test": "run-s format test:lib",
73
74
  "pre-commit": "lint-staged",
74
75
  "prepare": "run-s compile && husky"
75
76
  },
77
+ "dependencies": {
78
+ "ip-address": "^10.2.0"
79
+ },
76
80
  "peerDependencies": {
77
81
  "express": ">= 4.11"
78
82
  },
79
83
  "devDependencies": {
80
- "@biomejs/biome": "2.3.1",
84
+ "@biomejs/biome": "2.4.6",
81
85
  "@express-rate-limit/prettier": "1.1.1",
82
86
  "@express-rate-limit/tsconfig": "1.0.2",
83
- "@jest/globals": "30.2.0",
84
- "@types/express": "5.0.4",
87
+ "@jest/globals": "30.4.1",
88
+ "@types/express": "5.0.6",
85
89
  "@types/jest": "30.0.0",
86
- "@types/node": "24.9.1",
87
- "@types/supertest": "6.0.3",
88
- "del-cli": "6.0.0",
90
+ "@types/node": "25.7.0",
91
+ "@types/supertest": "7.2.0",
92
+ "del-cli": "7.0.0",
89
93
  "dts-bundle-generator": "8.1.2",
90
- "esbuild": "0.25.11",
91
- "express": "5.1.0",
94
+ "esbuild": "0.28.0",
95
+ "express": "5.2.1",
92
96
  "husky": "9.1.7",
93
- "jest": "30.2.0",
94
- "lint-staged": "16.2.6",
95
- "mintlify": "4.2.179",
97
+ "jest": "30.4.2",
98
+ "lint-staged": "17.0.4",
99
+ "mintlify": "4.2.559",
96
100
  "npm-run-all": "4.1.5",
97
- "prettier": "3.6.2",
101
+ "prettier": "3.8.3",
98
102
  "ratelimit-header-parser": "0.1.0",
99
- "supertest": "7.1.4",
100
- "ts-jest": "29.4.5",
103
+ "supertest": "7.2.2",
104
+ "ts-jest": "29.4.9",
101
105
  "ts-node": "10.9.2",
102
106
  "typescript": "5.9.3"
103
107
  },
@@ -105,8 +109,5 @@
105
109
  "lint-staged": {
106
110
  "*.{js,ts,json}": "biome check --write",
107
111
  "*.{md,yaml}": "prettier --write"
108
- },
109
- "dependencies": {
110
- "ip-address": "10.0.1"
111
112
  }
112
113
  }
package/readme.md CHANGED
@@ -66,24 +66,10 @@ default values.
66
66
  | [`skipFailedRequests`] | `boolean` | Uncount 4xx/5xx responses. |
67
67
  | [`requestWasSuccessful`] | `function` | Used by `skipSuccessfulRequests` and `skipFailedRequests`. |
68
68
  | [`validate`] | `boolean` \| `object` | Enable or disable built-in validation checks. |
69
+ | [`logger`] | `Logger` | Custom logger |
69
70
 
70
71
  ## Thank You
71
72
 
72
- Sponsored by [Zuplo](https://zuplo.link/express-rate-limit) a fully-managed API
73
- Gateway for developers. Add
74
- [dynamic rate-limiting](https://zuplo.link/dynamic-rate-limiting),
75
- authentication and more to any API in minutes. Learn more at
76
- [zuplo.com](https://zuplo.link/express-rate-limit)
77
-
78
- <p align="center">
79
- <a href="https://zuplo.link/express-rate-limit">
80
- <picture width="322">
81
- <source media="(prefers-color-scheme: dark)" srcset="https://github.com/express-rate-limit/express-rate-limit/assets/114976/cd2f6fa7-eae1-4fbb-be7d-b17df4c6f383">
82
- <img alt="zuplo-logo" src="https://github.com/express-rate-limit/express-rate-limit/assets/114976/66fd75fa-b39e-4a8c-8d7a-52369bf244dc" width="322">
83
- </picture>
84
- </a>
85
- </p>
86
-
87
73
  ---
88
74
 
89
75
  Thanks to Mintlify for hosting the documentation at
@@ -97,7 +83,7 @@ Thanks to Mintlify for hosting the documentation at
97
83
 
98
84
  ---
99
85
 
100
- Finally, thank you to everyone who's contributed to this project in any way! 🫶
86
+ And thank you to everyone who's contributed to this project in any way! 🫶
101
87
 
102
88
  ## Issues and Contributing
103
89
 
@@ -108,7 +94,7 @@ If you need help with something, feel free to
108
94
  [start a discussion](https://github.com/express-rate-limit/express-rate-limit/discussions/new)!
109
95
 
110
96
  If you wish to contribute to the library, thanks! First, please read
111
- [the contributing guide](https://express-rate-limit.mintlify.app/docs/guides/contributing.mdx).
97
+ [the contributing guide](https://express-rate-limit.mintlify.app/guides/contributing).
112
98
  Then you can pick up any issue and fix/implement it!
113
99
 
114
100
  ## License
@@ -149,3 +135,5 @@ MIT © [Nathan Friedly](http://nfriedly.com/),
149
135
  https://express-rate-limit.mintlify.app/reference/configuration#requestwassuccessful
150
136
  [`validate`]:
151
137
  https://express-rate-limit.mintlify.app/reference/configuration#validate
138
+ [`logger`]:
139
+ https://express-rate-limit.mintlify.app/reference/configuration#logger