Profiles·Public

express-rate-limit

semver>=6.0.0postconditions16functions3last verified2026-04-15coverage score100%

Postconditions — what we check

  • rateLimit · store-error-fail-closed
    error
    WhenThe backing store throws an error during hit counting (default passOnStoreError: false)
    ThrowsError forwarded to Express error handler via next(err)
    Required handlingApplication MUST register an Express error handler (4-argument middleware) to handle store failures. With default settings (fail-closed), a store error will block ALL requests until the error handler responds. Consider setting passOnStoreError: true for fail-open behavior in high-availability scenarios.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • rateLimit · store-error-fail-open
    warning
    WhenThe backing store throws an error and passOnStoreError: true is configured
    ReturnsRequest is allowed through without rate limiting; error is logged to console
    Required handlingNo action required — use the returned value as needed.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • rateLimit · rate-limit-exceeded
    info
    WhenClient has exceeded the configured limit within the windowMs time window
    ReturnsHTTP 429 response with configurable message (default: 'Too many requests, please try again later.') and Retry-After header
    Required handlingNo action required — use the returned value as needed.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • rateLimit · undefined-ip
    error
    Whenrequest.ip is undefined (often due to missing trust proxy configuration)
    ThrowsLogs ERR_ERL_UNDEFINED_IP_ADDRESS warning; may result in incorrect rate limiting behavior
    Required handlingIf deploying behind a reverse proxy (nginx, Cloudflare, load balancer), caller MUST set app.set('trust proxy', N) in Express before using rateLimit(), where N is the number of trusted proxies. Failure to do so causes request.ip to be undefined or incorrect, allowing clients to bypass rate limiting by spoofing headers.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • rateLimit · store-reuse
    error
    WhenA single store instance is shared across multiple rateLimit() middleware instances
    ThrowsLogs ERR_ERL_STORE_REUSE warning at startup
    Required handlingEach call to rateLimit() MUST use a separate store instance. Create a new store instance for each rate limiter to avoid shared hit count state between limiters.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • rateLimit · success-within-limit
    info
    WhenClient is within the rate limit
    ReturnsRequest is forwarded to next middleware. X-RateLimit-* headers are added to the response.
    Required handlingNo action required — use the returned value as needed.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • rateLimit · invalid-ip-address
    error
    Whenrequest.ip contains an invalid IPv4/IPv6 format or includes a port number (e.g. '127.0.0.1:3000'). Triggered by ERR_ERL_INVALID_IP_ADDRESS validation check.
    ThrowsLogs ERR_ERL_INVALID_IP_ADDRESS warning to console. The request is processed with the invalid key, which can cause bypass vulnerabilities — clients using port-mangled IPs are counted separately from normal IPs.
    Required handlingWhen deploying behind a proxy, ensure trust proxy is set correctly so Express normalizes request.ip. If using a custom keyGenerator, return ipKeyGenerator(req.ip) rather than raw req.ip to normalize the value. Invalid IP addresses can create bypass vulnerabilities that are invisible until exploited.
    costhighin prodsilent failureusers seesecurity breachvisibilitysilent
    Sources[2]
  • rateLimit · x-forwarded-for-without-trust-proxy
    error
    WhenX-Forwarded-For header is present in the request but Express trust proxy is not configured (default: false). Triggered by ERR_ERL_UNEXPECTED_X_FORWARDED_FOR validation check.
    ThrowsLogs ERR_ERL_UNEXPECTED_X_FORWARDED_FOR warning. Rate limiting uses the wrong IP — typically the load balancer's IP — causing all users to share a single rate limit bucket, effectively making the middleware a global limiter instead of a per-client limiter.
    Required handlingIf deploying behind a load balancer or reverse proxy that sets X-Forwarded-For, caller MUST configure app.set('trust proxy', N) where N is the number of trusted proxies in the chain. Without this, all clients appear to share the same IP, hitting the rate limit simultaneously.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • rateLimit · double-count
    error
    WhenThe same request increments the rate limit counter more than once. Triggered by ERR_ERL_DOUBLE_COUNT validation — typically caused by duplicate rate limiter middleware instances applied to the same route, or by sharing a store instance without a unique prefix across multiple limiters.
    ThrowsLogs ERR_ERL_DOUBLE_COUNT warning. Clients hit their rate limit at half the configured threshold, causing legitimate users to be blocked unexpectedly.
    Required handlingReview route registration to ensure each rate limiter is applied exactly once per request path. If sharing an external store across multiple limiter instances, set a unique prefix per instance (e.g., store: new RedisStore({ prefix: 'api-limiter:' })).
    costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • rateLimit · created-in-request-handler
    error
    WhenrateLimit() is called inside a request handler function (e.g., inside app.get() callback) rather than at application initialization time. Triggered by ERR_ERL_CREATED_IN_REQUEST_HANDLER.
    ThrowsLogs ERR_ERL_CREATED_IN_REQUEST_HANDLER warning at runtime. A new MemoryStore is created on each request, meaning hit counts are never accumulated — the rate limiter never actually limits anything.
    Required handlingAlways call rateLimit() at module/app initialization, not inside request handlers. The returned middleware instance should be created once and reused across requests. Example: const limiter = rateLimit({ ... }); app.use(limiter); // correct app.get('/route', (req, res) => { const limiter = rateLimit({ ... }); ... }); // wrong
    costhighin prodsilent failureusers seesecurity breachvisibilitysilent
    Sources[2]
  • rateLimit · rate-limit-invalid-store
    error
    WhenA store option is provided but is missing required interface methods: increment(), decrement(), or resetKey(). Also triggers if optional resetAll or init are present but are not functions.
    ThrowsTypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.") — thrown synchronously during rateLimit() call at startup
    Required handlingCaller MUST ensure custom stores implement the full Store interface. Validate in integration tests. This throws synchronously at startup — the process crashes before any requests are served.
    costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3][4]
  • rateLimit · async-callback-error-routes-to-handler
    error
    WhenAn async configuration callback (keyGenerator, skip, requestWasSuccessful, limit function, message function, or identifier function) throws or returns a rejected Promise during processing.
    ThrowsError forwarded to Express error handler via next(error). The middleware does NOT send an HTTP response — it calls next(error), routing to the nearest 4-argument Express error handler.
    Required handlingApplications MUST register a global Express error handler. Callback implementations should handle expected failures internally. Without a global error handler, Express sends a plain 500. Note: callback errors bypass passOnStoreError — they always route to next(error).
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3][1]
  • getKey · get-key-store-unsupported
    error
    WhenThe backing store does not implement the optional get() method. Many external store adapters (Redis, Memcached, PostgreSQL) do not implement get() by default.
    ThrowsThrows an Error: "The store does not support the 'get' method" (or equivalent from the adapter). Unhandled, this causes an unhandled promise rejection that crashes Node.js worker threads in production.
    Required handlingCallers of limiter.getKey() MUST wrap the call in a try-catch (or .catch()) to handle stores that do not implement get(). Since store support varies by adapter, defensive error handling is required regardless of which store adapter is in use. If the store doesn't support get(), fall back to allowing the request through or return a graceful degraded response.
    costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4]
  • getKey · get-key-store-error
    error
    WhenThe backing store's get() method throws an error (e.g., Redis connection lost, database timeout) while limiter.getKey() is awaited without error handling.
    ThrowsStore error propagates as an unhandled promise rejection. Unlike the middleware itself (which has passOnStoreError for fail-open behavior), getKey() has no built-in error isolation — the raw store error is thrown directly to the caller.
    Required handlingWrap getKey() in try-catch. On store error, implement a fail-open fallback (return default quota info or skip the pre-check) rather than blocking the request. Example: try { info = await limiter.getKey(key); } catch { info = undefined; }
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1]
  • resetKey · reset-key-unprotected-endpoint
    error
    WhenThe reset endpoint itself is subject to the same rate limiter it is trying to reset. A client that is already rate-limited (HTTP 429) cannot reach the reset handler — the rate limit middleware blocks the request before resetKey() is ever called.
    ThrowsNo error is thrown — the issue is architectural. resetKey() is never reached because the rate limiter blocks the request first. The unlock operation silently fails from the client's perspective.
    Required handlingAny endpoint that calls resetKey() MUST be exempt from the same rate limiter. Use the skip option: skip: (req) => req.path === '/admin/reset-limit' Or apply the rate limiter only to specific routes rather than globally.
    costmediumin prodsilent failureusers seeauthentication failurevisibilitysilent
    Sources[1]
  • resetKey · reset-key-wrong-key-format
    warning
    WhenresetKey() is called with a raw IP address string when the rate limiter was configured with a custom keyGenerator that produces a different key format (e.g., user ID, API key, or ipKeyGenerator() with IPv6 subnet masking). The key does not match any stored entry.
    ThrowsNo error is thrown — the operation silently no-ops. The client's hit count is not reset. This is a silent failure that is particularly dangerous in account unlock flows.
    Required handlingAlways call resetKey() with the same key format that keyGenerator produces. If using IP-based limiting, call ipKeyGenerator(req.ip) to normalize before passing to resetKey(). If using user-ID-based limiting, pass the user ID. Verify the key format matches by testing with getKey() after calling resetKey() to confirm the counter was actually cleared.
    costlowin prodsilent failureusers seeauthentication failurevisibilitysilent
    Sources[4]

Sources

Every postcondition cites at least one of these. Numbered to match the footnotes above.

  1. [1]express-rate-limit.mintlify.app/reference/configurationhttps://express-rate-limit.mintlify.app/reference/configuration
  2. [2]express-rate-limit.mintlify.app/reference/error-codeshttps://express-rate-limit.mintlify.app/reference/error-codes
  3. [3]github.com/express-rate-limit/express-rate-limithttps://github.com/express-rate-limit/express-rate-limit/blob/main/source/rate-limit.ts
  4. [4]express-rate-limit.mintlify.app/guides/creating-a-storehttps://express-rate-limit.mintlify.app/guides/creating-a-store
Need a different package?
Request a profile