express-rate-limit vs rate-limiter-flexible vs express-slow-down vs express-brute vs express-limiter
Rate Limiting and Request Throttling in Express.js Applications
express-rate-limitrate-limiter-flexibleexpress-slow-downexpress-bruteexpress-limiterSimilar Packages:

Rate Limiting and Request Throttling in Express.js Applications

These five packages help protect Express.js applications from abuse by controlling how many requests clients can make. express-rate-limit is the most popular and actively maintained solution for basic rate limiting. express-slow-down works alongside it to gradually slow responses instead of blocking them. rate-limiter-flexible offers framework-agnostic flexibility with multiple storage backends. express-brute and express-limiter are older solutions with limited maintenance — they work but lack modern features and security updates.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express-rate-limit47,955,7863,266146 kB10a month agoMIT
rate-limiter-flexible2,748,4313,566230 kB919 days agoISC
express-slow-down181,28230037.6 kB14 months agoMIT
express-brute18,524568-2110 years agoBSD
express-limiter13,945423-219 years agoMIT

Rate Limiting in Express.js: Five Packages Compared

Protecting your Express.js applications from abuse requires smart rate limiting. These five packages (express-brute, express-limiter, express-rate-limit, express-slow-down, rate-limiter-flexible) all tackle this problem — but they work differently and suit different situations. Let's break down how they compare.

⚠️ Maintenance Status: Active vs Legacy

Before diving into features, know which packages are still maintained.

express-brute and express-limiter are legacy packages with minimal recent updates. They work, but don't expect new features or security patches.

express-rate-limit, express-slow-down, and rate-limiter-flexible are actively maintained with regular updates and better long-term support.

💡 Recommendation: For new projects, skip express-brute and express-limiter. Use express-rate-limit for simplicity or rate-limiter-flexible for advanced needs.

🔒 Basic Rate Limiting: Blocking Excessive Requests

All five packages can block clients who send too many requests. Here's how each handles it.

express-brute uses a brute-force protection approach with memory or Redis stores.

// express-brute: Basic setup
const ExpressBrute = require('express-brute');
const store = new ExpressBrute.MemoryStore();
const bruteforce = new ExpressBrute(store);

app.post('/login', bruteforce.prevent, (req, res) => {
  res.send('Login attempt processed');
});

express-limiter integrates with Redis for distributed rate limiting.

// express-limiter: Redis-backed limiting
const limiter = require('express-limiter')(app, redisClient);

app.use(limiter({
  path: '*',
  method: '*',
  limit: 100,
  expire: 60000
}));

express-rate-limit offers the cleanest API for standard rate limiting.

// express-rate-limit: Simple configuration
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests, please try again later'
});

app.use('/api/', limiter);

express-slow-down doesn't block — it adds delay instead (see next section).

rate-limiter-flexible provides framework-agnostic rate limiting with multiple store options.

// rate-limiter-flexible: Flexible configuration
const { RateLimiterMemory } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterMemory({
  points: 10, // 10 requests
  duration: 1 // per 1 second
});

app.use(async (req, res, next) => {
  try {
    await rateLimiter.consume(req.ip);
    next();
  } catch (rej) {
    res.status(429).send('Too Many Requests');
  }
});

🐌 Slow Down vs Hard Block: Graceful Degradation

Sometimes you want to slow abusive clients instead of blocking them outright.

express-brute blocks after a threshold — no gradual slowdown.

// express-brute: Hard block after failures
bruteforce.handle((req, res, next, nextValidRequestDate) => {
  res.status(429).send('Too many requests');
});

express-limiter also blocks — no built-in slowdown feature.

// express-limiter: Returns 429 when limit exceeded
// No delay option available

express-rate-limit blocks by default, but you can customize the response.

// express-rate-limit: Custom block response
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  handler: (req, res) => {
    res.status(429).json({ error: 'Rate limit exceeded' });
  }
});

express-slow-down adds increasing delays before blocking occurs.

// express-slow-down: Gradual delay
const slowDown = require('express-slow-down');

const speedLimiter = slowDown({
  windowMs: 15 * 60 * 1000,
  delayAfter: 50, // start delaying after 50 requests
  delayMs: 1000 // add 1 second delay per request
});

app.use('/api/', speedLimiter);

rate-limiter-flexible lets you implement custom slowdown logic manually.

// rate-limiter-flexible: Custom delay implementation
const rateLimiter = new RateLimiterMemory({
  points: 100,
  duration: 60,
  execEvenly: true // spreads requests evenly
});

// You can add custom delay logic based on remaining points

🗄️ Storage Backends: Memory, Redis, and Beyond

Where rate limit data is stored affects scalability and reliability.

express-brute supports MemoryStore and RedisStore.

// express-brute: Redis store
const RedisStore = require('express-brute-redis');
const store = new RedisStore({
  host: 'localhost',
  port: 6379
});

express-limiter requires Redis — no memory store option.

// express-limiter: Redis only
const limiter = require('express-limiter')(app, redisClient);
// Must provide Redis client

express-rate-limit works with memory (default) or Redis via external stores.

// express-rate-limit: Memory store (default)
const limiter = rateLimit({ windowMs: 60000, max: 100 });

// express-rate-limit: Redis store (via rate-limit-redis)
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({
  store: new RedisStore({ client: redisClient })
});

express-slow-down uses the same store options as express-rate-limit.

// express-slow-down: Redis store
const RedisStore = require('rate-limit-redis');
const slowDown = require('express-slow-down');

const speedLimiter = slowDown({
  store: new RedisStore({ client: redisClient })
});

rate-limiter-flexible supports the most stores: Memory, Redis, Memcached, MongoDB, PostgreSQL, and more.

// rate-limiter-flexible: Multiple store options
const { RateLimiterRedis } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  points: 10,
  duration: 1
});

// Also supports: RateLimiterMemcache, RateLimiterMongo, RateLimiterPostgres

🎯 Key Identification: IP, User ID, or Custom Keys

How you identify clients changes based on your use case.

express-brute uses IP by default, with custom key support.

// express-brute: Custom key function
const bruteforce = new ExpressBrute(store, {
  getKey: (req, res, next) => next(req.body.username) // limit by username
});

express-limiter uses IP or custom lookup.

// express-limiter: Custom lookup
app.use(limiter({
  lookup: 'body.username' // limit by username in request body
}));

express-rate-limit uses IP by default, customizable via keyGenerator.

// express-rate-limit: Custom key generator
const limiter = rateLimit({
  keyGenerator: (req) => {
    return req.user?.id || req.ip; // limit by user ID if logged in
  }
});

express-slow-down uses the same keyGenerator option as express-rate-limit.

// express-slow-down: Custom key generator
const speedLimiter = slowDown({
  keyGenerator: (req) => {
    return req.user?.id || req.ip;
  }
});

rate-limiter-flexible gives you full control over key identification.

// rate-limiter-flexible: Manual key control
const key = req.user?.id || req.ip;
await rateLimiter.consume(key);
// You decide the key completely

📊 Feature Comparison Table

Featureexpress-bruteexpress-limiterexpress-rate-limitexpress-slow-downrate-limiter-flexible
Maintenance❌ Legacy❌ Legacy✅ Active✅ Active✅ Active
Default StoreMemoryRedisMemoryMemoryMemory
Redis Support✅ (external)✅ (external)
Other StoresLimitedLimited✅ Many
Slow Down⚠️ Manual
Hard Block⚠️ After delay
Custom Keys
Framework Agnostic
Ease of UseMediumMediumHighHighMedium

🌐 Real-World Scenarios

Scenario 1: Login Endpoint Protection

You need to prevent brute-force attacks on your login route.

  • Best choice: express-rate-limit
  • Why? Simple setup, blocks after threshold, well-documented.
const loginLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 attempts per hour
  message: 'Too many login attempts, try again in an hour'
});

app.post('/login', loginLimiter, loginHandler);

Scenario 2: Public API with Graceful Degradation

You run a public API and want to discourage abuse without blocking legitimate users.

  • Best choice: express-slow-down + express-rate-limit
  • Why? Slow down first, block only if they keep pushing.
const slowDown = require('express-slow-down');
const rateLimit = require('express-rate-limit');

const speedLimiter = slowDown({
  windowMs: 15 * 60 * 1000,
  delayAfter: 50,
  delayMs: 500
});

const hardLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
});

app.use('/api/', speedLimiter, hardLimiter);

Scenario 3: Multi-Service Architecture

You have microservices using Express, Koa, and Fastify — need consistent rate limiting.

  • Best choice: rate-limiter-flexible
  • Why? Framework-agnostic, works across all services with same logic.
const { RateLimiterRedis } = require('rate-limiter-flexible');

const rateLimiter = new RateLimiterRedis({
  storeClient: redisClient,
  points: 100,
  duration: 60
});

// Use same limiter in Express, Koa, Fastify services

Scenario 4: Legacy Application Maintenance

You're maintaining an old Express app already using express-brute.

  • Best choice: Keep express-brute (for now)
  • Why? Rewriting adds risk. Plan migration to express-rate-limit later.
// Keep existing express-brute setup
// Plan migration during next major refactor

Scenario 5: User-Based Rate Limiting

You need to limit requests per user account, not per IP.

  • Best choice: express-rate-limit or rate-limiter-flexible
  • Why? Both support custom key generators for user IDs.
// express-rate-limit approach
const userLimiter = rateLimit({
  keyGenerator: (req) => req.user?.id || req.ip
});

// rate-limiter-flexible approach
const key = req.user?.id || req.ip;
await rateLimiter.consume(key);

🚫 When Not to Use These

Consider alternatives when:

  • You need DDoS protection at the network level — use Cloudflare, AWS WAF, or nginx instead.
  • Your app runs on serverless/edge — check platform-specific rate limiting (Vercel, Cloudflare Workers).
  • You need complex business rules — build custom middleware with Redis directly.
  • Your traffic is very low — simple middleware might be overkill.

💡 Final Recommendation

Think about your project's needs:

  • Starting a new Express project? → Use express-rate-limit. It's simple, maintained, and covers 90% of use cases.
  • Need gradual slowdown? → Add express-slow-down alongside express-rate-limit.
  • Multiple frameworks or advanced stores? → Choose rate-limiter-flexible.
  • Maintaining legacy code? → Keep express-brute or express-limiter temporarily, but plan migration.

Final Thought: Rate limiting protects your application, but the right tool depends on your architecture. For most Express.js projects, express-rate-limit offers the best balance of simplicity and power. Save rate-limiter-flexible for complex scenarios, and avoid legacy packages in new code.

How to Choose: express-rate-limit vs rate-limiter-flexible vs express-slow-down vs express-brute vs express-limiter

  • express-rate-limit:

    Choose express-rate-limit for most Express.js projects needing straightforward rate limiting. It has excellent documentation, active maintenance, and works well with Redis or memory stores. Ideal for APIs, login endpoints, and general request throttling where you need to block excessive requests cleanly.

  • rate-limiter-flexible:

    Choose rate-limiter-flexible when you need framework-agnostic rate limiting or advanced features like multiple stores, custom keys, or complex rules. Works with Express, Koa, Fastify, and more. Best for microservices, multi-framework projects, or when you need fine-grained control over rate limiting logic.

  • express-slow-down:

    Choose express-slow-down when you want to degrade service gradually instead of hard blocking. Works best paired with express-rate-limit — slow down first, then block if clients keep pushing. Great for public APIs where you want to discourage abuse without immediately rejecting legitimate users who hit limits accidentally.

  • express-brute:

    Choose express-brute only for legacy projects already using it. This package is no longer actively maintained and lacks modern security features. For new projects, prefer express-rate-limit or rate-limiter-flexible which receive regular updates and have better community support.

  • express-limiter:

    Choose express-limiter only if you're maintaining an existing codebase that depends on it. The package has minimal recent activity and fewer configuration options than modern alternatives. New projects should use express-rate-limit for better documentation and ongoing maintenance.

README for express-rate-limit

express-rate-limit

tests npm version npm downloads license

Basic rate-limiting middleware for Express. Use to limit repeated requests to public APIs and/or endpoints such as password reset. Plays nice with express-slow-down and ratelimit-header-parser.

Usage

The full documentation is available on-line.

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', // draft-6: `RateLimit-*` headers; draft-7 & draft-8: combined `RateLimit` header
	legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
	ipv6Subnet: 56, // Set to 60 or 64 to be less aggressive, or 52 or 48 to be more aggressive
	// store: ... , // Redis, Memcached, etc. See below.
})

// Apply the rate limiting middleware to all requests.
app.use(limiter)

Data Stores

The rate limiter comes with a built-in memory store, and supports a variety of external data stores.

Configuration

All function options may be async. Click the name for additional info and default values.

OptionTypeRemarks
windowMsnumberHow long to remember requests for, in milliseconds.
limitnumber | functionHow many requests to allow.
messagestring | json | functionResponse to return after limit is reached.
statusCodenumberHTTP status code after limit is reached (default is 429).
handlerfunctionFunction to run after limit is reached (overrides message and statusCode settings, if set).
legacyHeadersbooleanEnable the X-Rate-Limit header.
standardHeaders'draft-6' | 'draft-7' | 'draft-8'Enable the Ratelimit header.
identifierstring | functionName associated with the quota policy enforced by this rate limiter.
storeStoreUse a custom store to share hit counts across multiple nodes.
passOnStoreErrorbooleanAllow (true) or block (false, default) traffic if the store becomes unavailable.
keyGeneratorfunctionIdentify users (defaults to IP address).
ipv6Subnetnumber (32-64) | function | falseHow many bits of IPv6 addresses to use in default keyGenerator
requestPropertyNamestringAdd rate limit info to the req object.
skipfunctionReturn true to bypass the limiter for the given request.
skipSuccessfulRequestsbooleanUncount 1xx/2xx/3xx responses.
skipFailedRequestsbooleanUncount 4xx/5xx responses.
requestWasSuccessfulfunctionUsed by skipSuccessfulRequests and skipFailedRequests.
validateboolean | objectEnable or disable built-in validation checks.
loggerLoggerCustom logger

Thank You


Thanks to Mintlify for hosting the documentation at express-rate-limit.mintlify.app

Create your docs today


And thank you to everyone who's contributed to this project in any way! 🫶

Issues and Contributing

If you encounter a bug or want to see something added/changed, please go ahead and open an issue! If you need help with something, feel free to start a discussion!

If you wish to contribute to the library, thanks! First, please read the contributing guide. Then you can pick up any issue and fix/implement it!

License

MIT © Nathan Friedly, Vedant K