Open-source project
express-rate-limit/express-rate-limit avatar
express-rate-limit/express-rate-limit

express-rate-limit: IP rate limiting as Express middleware

Basic rate-limiting middleware for the Express web server

3,310 stars264 forksTypeScriptMIT

At a glance

What is it?
express-rate-limit is a TypeScript middleware that counts requests per client and returns 429 once a quota is spent. It ships an in-memory store, supports external stores for multi-node deployments, and leaves the identity of a client up to you.
Who is it for?
Adopt express-rate-limit when you run an Express app that needs per-client throttling on login, password reset or a public API and you accept that the default memory store only counts within one process. Do not adopt it if your traffic is not served by Express, or if you need a distributed quota and cannot run an external store.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 9, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What express-rate-limit counts, and for whom

The package describes itself as basic rate-limiting middleware for Express, aimed at repeated requests to public APIs and endpoints such as password reset. That phrasing sets the scope precisely. It is not a WAF, not a bot detector, and not a queue. It counts incoming requests, decides whether the count for a given client is over a quota inside a time window, and either calls the next handler or ends the response with status 429 by default.

The intended user is a Node.js developer who already has an Express application and wants a few lines of protection on the routes that get abused: login, signup, password reset, OTP verification, and any unauthenticated read endpoint that is cheap to call and expensive to serve. The repository's package keywords list brute force and auth alongside rate limiting, which matches that framing. It is a middleware, so it composes with whatever else is in the stack rather than replacing it.

One design decision is worth stating plainly: the limiter does not know who your users are. By default it keys on the client IP address, and the option to change that is a function you supply. Everything about fairness, NAT, shared offices and mobile carriers follows from that choice, and the README treats it as a configuration matter rather than a problem the library solves for you.

The request path through the limiter

The mechanism is a counter keyed by client, evaluated once per request. When the middleware runs, it derives a key, asks the store for the current hit count for that key inside the current window, increments it, and compares the result against the limit. Under the limit, the request continues down the Express stack. At or over the limit, the handler runs instead.

Two layers are configurable. The keyGenerator function decides what a client is; the store decides where counts live. The built-in memory store keeps hits in the process, which is why the README points at external data stores for sharing hit counts across multiple nodes. That sentence is the whole story of horizontal scaling here: one process, one set of counters. Run four Node processes behind a load balancer with the memory store and each process enforces its own quota, so the effective limit is roughly four times what you configured.

The response side has its own choices. standardHeaders accepts 'draft-6', 'draft-7' or 'draft-8', and the README notes that draft-6 emits the RateLimit-* headers while draft-7 and draft-8 emit a combined RateLimit header. legacyHeaders toggles the older X-RateLimit-* headers. The identifier option names the quota policy, which matters when you attach several limiters with different windows to the same app, and requestPropertyName puts the rate limit info onto the req object so a downstream handler can read it.

There is also a validation layer. The validate option, boolean or object, enables or disables built-in checks, and a logger option accepts a custom logger. The README does not enumerate which misconfigurations those checks catch, so treat the warnings as a signal to read the documentation page rather than as a complete safety net.

Installing express-rate-limit and limiting one route

The package is published on npm as express-rate-limit, and package.json declares engines.node as ">= 16". The README's usage example is TypeScript and imports the named rateLimit export. Install it into an existing Express project first.

bash
npm install express-rate-limit

The README gives this example, which applies a limiter to every request. windowMs is in milliseconds, limit is the number of requests allowed per window, and the two header options control which response headers are emitted.

ts
import { rateLimit } from 'express-rate-limit'

const limiter = rateLimit({
	windowMs: 15 * 60 * 1000, // 15 minutes
	limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
	standardHeaders: 'draft-8',
	legacyHeaders: false,
	ipv6Subnet: 56,
})

app.use(limiter)

After this, the 101st request from the same client inside fifteen minutes receives a 429 with the configured message, and the responses carry the draft-8 RateLimit header. The ipv6Subnet value of 56 is the README's own example; the comment next to it says to use 60 or 64 to be less aggressive and 52 or 48 to be more aggressive, because IPv6 clients are grouped by prefix rather than by full address.

For a login route, the sensible first move is to scope the limiter to that path instead of the whole app, and to tighten the window. The README does not show a route-scoped example, but the middleware is ordinary Express middleware, so passing it as the second argument of a route is the same mechanism it documents with app.use. What you should see in either arrangement is a 429 on the request that crosses the quota, not a connection reset and not a silently dropped request.

Where this limiter stops being the right tool

The memory store is the first real limitation, and it is a correctness problem rather than a performance one. Counts live in the process, so any deployment with more than one Node worker or more than one container enforces the quota per process. The README's answer is an external store, and it links to a stores reference. Until you configure one, your configured limit is not the limit your users experience.

The second limitation is identity. An IP address is a proxy for a client, and it fails in both directions. Behind a proxy or CDN, if Express is not configured to trust that proxy, the default keyGenerator sees the proxy's address and every user shares one counter. The related searches around trust proxy point at exactly this failure. In the other direction, a shared corporate NAT or a mobile carrier gateway puts many real users behind one address, so a limit tuned for one person throttles a crowd. The library exposes keyGenerator and skip so you can key on an authenticated user id or exempt health checks, but it does not decide this for you.

Third, storage failures have a defined but blunt behaviour. passOnStoreError defaults to false according to the README's option table, meaning traffic is blocked when the store becomes unavailable. That is a deliberate fail-closed stance, and it is the right one for a login endpoint and the wrong one for a public read API that must stay up during a Redis outage. The option exists to flip it; the default is not neutral.

Finally, this is not a substitute for authentication, input validation or upstream filtering. A limiter reduces how often an attacker can try something. It does not make a weak password policy safe, and it does not stop a distributed attempt that stays under the per-client threshold. If your threat model includes that, you need something at a different layer.

Alternatives and the difference in approach

The README itself points at two neighbours. express-slow-down is the closest relative: instead of rejecting the request once the quota is spent, it delays the response, so a client that keeps calling gets progressively slower rather than a 429. For an API consumed by scripts, the delay is often a gentler signal than an error, and the two packages are described as playing nicely together, which suggests running both: slow down first, then reject. If your problem is a noisy scraper rather than a credential-stuffing attempt, slowing is frequently the better fit.

The second neighbour is ratelimit-header-parser, which reads rate limit headers. That is a client-side concern, useful when your own service calls an upstream API that publishes its quota, and it solves a different problem than enforcement.

Outside the Express ecosystem, the alternative most teams reach for is a reverse proxy or API gateway that applies rate limiting before the request reaches Node. The difference in approach is where the counter lives and what it can see. A gateway counts at the edge, shares state across all your instances by construction, and protects every service behind it, including ones that are not Express. Middleware counts inside the application, which means it can key on an authenticated user id, read application state in the skip function, and return your API's own error shape. If your services are polyglot, the gateway is the more consistent place. If you have one Express app and want per-user quotas tied to your auth layer, the middleware sees things the edge cannot.

Maintenance, versioning and the MIT licence

The repository is not archived, and the last push was on 2026-09-07. Recent releases are v8.6.1 on 2026-07-26, v8.6.2 on 2026-08-04, and v8.7.0 on 2026-08-29, so the project is on the 8.x line with patch and minor releases arriving over the past months. The version in package.json is 8.7.0, and the package is ESM-first: "type": "module", with an exports map that provides separate import and require entries plus .d.mts, .d.cts and .d.ts type files, and a build that bundles CJS and ESM with esbuild and generates types with dts-bundle-generator. Node 16 or newer is required.

Upgrade cost is mostly the major versions. The README's own options table shows standardHeaders accepting 'draft-6', 'draft-7' and 'draft-8', which is a sign that header behaviour has moved as the IETF drafts changed; if you pin a draft value, expect to revisit it. The README does not document a migration path between major versions, so before moving from 7.x to 8.x, check the online documentation and the release notes rather than assuming a drop-in replacement.

The licence is MIT, and package.json declares it as such, with copyright attributed to Nathan Friedly and Vedant K. MIT is permissive: it allows commercial use, modification and redistribution provided the copyright notice and permission notice are kept. That is a description of the licence text, not legal advice; if your organisation has rules about attribution in distributed artefacts, route the license file through whoever handles that.

Editorial conclusion

Adopt express-rate-limit when you run an Express app that needs per-client throttling on login, password reset or a public API and you accept that the default memory store only counts within one process. Do not adopt it if your traffic is not served by Express, or if you need a distributed quota and cannot run an external store. Before deploying, verify three things: that your Express instance sets trust proxy correctly, because the default keyGenerator reads the client IP; that windowMs and limit match the endpoint you are protecting rather than a global default; and that passOnStoreError is set the way you want, since the documented default is false, meaning blocked traffic when the store is unavailable.

Frequently asked questions

How do I install express-rate-limit?

Install it from npm with npm install express-rate-limit inside your Express project. The package requires Node 16 or newer, and the README imports the named rateLimit export.

How does express-rate-limit work?

It is Express middleware that derives a key for each request, by default the client IP address, and asks a store for the hit count inside the current window. Under the limit the request continues; at or over the limit the handler runs and the default response status is 429.

How can I use express-rate-limit from npmjs?

The package is published on npm as express-rate-limit, with the homepage pointing at npmjs.com/package/express-rate-limit. You add it as a dependency, call rateLimit with options such as windowMs and limit, and pass the result to app.use or to a specific route.

What is a good API rate limit to set with express-rate-limit?

The README's example uses windowMs: 15 * 60 * 1000 and limit: 100, which is 100 requests per IP per fifteen minutes. The right value depends on the endpoint: a password reset route tolerates far fewer calls than a public read endpoint, and the README provides skip and keyGenerator to narrow who and what is counted.

What does express-rate-limit do?

It limits repeated requests to Express routes, most often public APIs and endpoints such as password reset. The README describes it as basic rate-limiting middleware and shows it applied with app.use.

Is express-rate-limit a good choice?

The README positions it as basic middleware with a built-in memory store and support for external stores, which covers single-process apps and, with a store, multi-node ones. It is the wrong tool if you need limiting outside Express or a distributed quota without an external store.

Official sources

  1. express-rate-limit/express-rate-limit on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/express-rate-limit-express-rate-limit.svg)](https://hysenlabs.com/projects/express-rate-limit-express-rate-limit)