express-brute、express-limiter、express-rate-limit、express-slow-down、rate-limiter-flexible は、すべて Express.js アプリケーションでレート制限(アクセス制限)を実装するためのミドルウェアパッケージです。これらは API エンドポイントへの過剰なリクエストを防ぎ、DDoS 攻撃やブルートフォース攻撃からアプリケーションを保護します。express-rate-limit が最も広く採用されており、rate-limiter-flexible は柔軟なストア対応が特徴です。express-slow-down はリクエストをブロックせず速度を落とすアプローチを取ります。
Express.js アプリケーションで API 保護を実装する際、適切なレート制限パッケージの選択はセキュリティとパフォーマンスに直結します。express-brute、express-limiter、express-rate-limit、express-slow-down、rate-limiter-flexible の 5 つのパッケージは、それぞれ異なるアプローチと特徴を持っています。実務での選択基準を明確にするために、技術的な違いを深掘りします。
express-brute は 2015 年頃から存在する古いパッケージで、現在はアクティブなメンテナンスが行われていません。
// express-brute: 古い API スタイル
const ExpressBrute = require('express-brute');
const store = new ExpressBrute.MemoryStore();
const bruteforce = new ExpressBrute(store);
app.post('/login', bruteforce.prevent, loginHandler);
express-limiter も同様に更新が停滞しており、Redis 特化型の制限ミドルウェアです。
// express-limiter: Redis 必須
const expressLimiter = require('express-limiter');
const redis = require('redis');
const client = redis.createClient();
expressLimiter(app, client, {
lookup: 'connection.remoteAddress',
total: 100,
expire: 1000 * 60 * 60
});
express-rate-limit は現在もアクティブにメンテナンスされており、npm で最もダウンロードされています。
// express-rate-limit: 現代的な API
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'リクエストが多すぎます'
});
app.use('/api/', limiter);
express-slow-down は express-rate-limit の作者によって作成され、同じエコシステムで動作します。
// express-slow-down: 遅延による制限
const slowDown = require('express-slow-down');
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50,
delayMs: 500
});
app.use('/api/', speedLimiter);
rate-limiter-flexible は最新の設計で、複数のストレージバックエンドを公式にサポートしています。
// rate-limiter-flexible: 柔軟なストア対応
const { RateLimiterRedis } = require('rate-limiter-flexible');
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 10,
duration: 1
});
app.use('/api/', (req, res, next) => {
rateLimiter.consume(req.ip)
.then(() => next())
.catch(() => res.status(429).send('制限超過');
});
ストレージ選択は、アプリケーションのスケールに直接影響します。
| パッケージ | メモリ | Redis | MongoDB | Memcached | その他 |
|---|---|---|---|---|---|
express-brute | ✅ | ✅ | ❌ | ❌ | カスタムストア可能 |
express-limiter | ❌ | ✅ | ❌ | ❌ | Redis のみ |
express-rate-limit | ✅ | ✅ | ✅ | ✅ | 公式ストア多数 |
express-slow-down | ✅ | ✅ | ✅ | ✅ | express-rate-limit と共通 |
rate-limiter-flexible | ✅ | ✅ | ✅ | ✅ | 15+ ストア対応 |
express-rate-limit のストア設定例:
const RedisStore = require('rate-limit-redis');
const { createClient } = require('redis');
const limiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args)
}),
windowMs: 15 * 60 * 1000,
max: 100
});
rate-limiter-flexible のストア設定例:
const { RateLimiterMongo } = require('rate-limiter-flexible');
const { MongoClient } = require('mongodb');
const rateLimiter = new RateLimiterMongo({
storeClient: mongoClient,
tableName: 'rateLimits',
points: 10,
duration: 1
});
各パッケージは異なる方法でリクエストをカウントし、制限を適用します。
express-brute は失敗カウントに特化しており、ログイン試行などのブルートフォース対策に適しています。
// express-brute: 失敗時のみカウント
bruteforce.handleFailures((req, res, next, nextValidRequestDate) => {
if (req.body.username === 'admin') {
return res.status(403).send('アカウントロック中');
}
next();
});
express-limiter は単純なカウントベースで、Redis の TTL 機能を利用します。
// express-limiter: シンプルカウント
expressLimiter(app, client, {
lookup: 'headers.api-key',
total: 1000,
expire: 1000 * 60 * 60,
onRateLimited: (req, res) => {
res.status(429).send('レート制限超過');
}
});
express-rate-limit は柔軟なキー生成とカスタムメッセージをサポートします。
// express-rate-limit: カスタムキー生成
const limiter = rateLimit({
keyGenerator: (req) => {
return req.user?.id || req.ip;
},
handler: (req, res) => {
res.status(429).json({
error: '制限超過',
retryAfter: Math.ceil(res.get('Retry-After'))
});
}
});
express-slow-down はブロックではなく遅延を適用します。
// express-slow-down: 段階的遅延
const speedLimiter = slowDown({
delayAfter: 50,
delayMs: (hits) => Math.min(hits * 100, 60000),
skipFailedRequests: true
});
rate-limiter-flexible はポイントベースのシステムで、異なるアクションに異なるコストを設定できます。
// rate-limiter-flexible: ポイントベース
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 100,
duration: 60
});
// 重い操作は更多ポイント消費
await rateLimiter.consume(userId, 5); // 5 ポイント消費
制限超過時のレスポンス制御は、ユーザー体験に影響します。
express-brute はカスタムエラーハンドラを登録します:
bruteforce.getMiddleware({
onBlocked: (req, res, next, nextValidRequestDate) => {
res.status(429).json({
error: '一時的にブロックされています',
retryAfter: nextValidRequestDate
});
}
});
express-rate-limit は標準 HTTP ヘッダーを自動設定します:
// 自動設定されるヘッダー
// X-RateLimit-Limit: 100
// X-RateLimit-Remaining: 95
// X-RateLimit-Reset: 1640000000
// Retry-After: 60
rate-limiter-flexible は詳細な制限情報を返します:
try {
const response = await rateLimiter.consume(key);
res.set('Retry-After', Math.ceil(response.msBeforeNext / 1000));
next();
} catch (rej) {
res.set('Retry-After', Math.ceil(rej.msBeforeNext / 1000));
res.status(429).json({
pointsRemaining: rej.remainingPoints,
msBeforeNext: rej.msBeforeNext
});
}
推奨:express-rate-limit
設定がシンプルで、デフォルトのメモリストアでも十分な場合:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
推奨:express-rate-limit または rate-limiter-flexible
複数サーバー環境で Redis を使用する場合:
// express-rate-limit + Redis
const limiter = rateLimit({
store: new RedisStore({ sendCommand: redisClient.sendCommand }),
windowMs: 15 * 60 * 1000,
max: 100
});
// rate-limiter-flexible + Redis
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 100,
duration: 900
});
推奨:rate-limiter-flexible
異なるエンドポイントで異なる制限を適用する場合:
const loginLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 5,
duration: 300
});
const apiLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 1000,
duration: 3600
});
app.post('/login', async (req, res, next) => {
try {
await loginLimiter.consume(req.ip);
next();
} catch {
res.status(429).send('ログイン試行が多すぎます');
}
});
推奨:express-slow-down + express-rate-limit 併用
いきなりブロックせず、段階的に制限:
const limiter = rateLimit({ windowMs: 900000, max: 100 });
const speedLimiter = slowDown({ windowMs: 900000, delayAfter: 50, delayMs: 200 });
app.use('/api/', speedLimiter);
app.use('/api/', limiter);
express-brute と express-limiter は、新しいプロジェクトでの使用を避けるべきです:
既存システムで使用している場合は、移行計画を立てることを推奨します:
// 移行例:express-brute → express-rate-limit
// 旧コード
app.post('/login', bruteforce.prevent, loginHandler);
// 新コード
const loginLimiter = rateLimit({
windowMs: 5 * 60 * 1000,
max: 5,
message: 'ログイン試行が多すぎます'
});
app.post('/login', loginLimiter, loginHandler);
| 機能 | express-brute | express-limiter | express-rate-limit | express-slow-down | rate-limiter-flexible |
|---|---|---|---|---|---|
| メンテナンス | ❌ 停滞 | ❌ 停滞 | ✅ アクティブ | ✅ アクティブ | ✅ アクティブ |
| Redis 対応 | ✅ | ✅ | ✅ | ✅ | ✅ |
| メモリストア | ✅ | ❌ | ✅ | ✅ | ✅ |
| カスタムキー | ⚠️ 限定的 | ⚠️ 限定的 | ✅ | ✅ | ✅ |
| ポイントシステム | ❌ | ❌ | ❌ | ❌ | ✅ |
| 遅延機能 | ❌ | ❌ | ❌ | ✅ | ⚠️ 要実装 |
| TypeScript | ❌ | ❌ | ✅ | ✅ | ✅ |
| 新規推奨 | ❌ | ❌ | ✅ | ✅ | ✅ |
新規プロジェクトでの選択フロー:
express-rate-limitexpress-rate-limit または rate-limiter-flexiblerate-limiter-flexibleexpress-slow-down + express-rate-limitexpress-rate-limit へ重要な注意点:
express-brute と express-limiter は新規プロジェクトで使用しないでくださいレート制限パッケージの選択は、アプリケーションの規模、インフラ構成、セキュリティ要件によって異なります。express-rate-limit が最もバランスの取れた選択肢ですが、大規模システムでは rate-limiter-flexible の柔軟性が活きます。ユーザー体験を重視する場合は express-slow-down の併用を検討してください。
express-rate-limit は最も成熟しており、コミュニティサポートが厚いパッケージです。シンプルな設定で始められ、メモリストアから Redis まで対応しています。新規プロジェクトで迷った場合の第一選択肢として推奨され、ドキュメントも充実しています。
rate-limiter-flexible は最も柔軟で、Redis、Memcached、MongoDB など複数のストアをサポートします。マイクロサービス環境や複雑な制限ロジックが必要な場合に適しています。学習コストはやや高いですが、大規模システムでの拡張性が高いです。
express-slow-down はリクエストを完全にブロックせず、応答を遅延させるアプローチを取ります。ユーザー体験を損なわずに悪意のあるトラフィックを抑制したい場合に適しています。express-rate-limit と併用して、段階的な制限を実現できます。
express-limiter は Redis をストレージバックエンドとして使用する場合に適しています。Redis 環境が既に整っており、シンプルな設定で済ませたいプロジェクトに向いています。ただし、複数のストレージオプションを必要とする場合は rate-limiter-flexible の方が柔軟です。
express-brute は古いパッケージで、現在はメンテナンスが停滞しています。ブルートフォース攻撃対策に特化していますが、新しいプロジェクトでの使用は推奨されません。既存のレガシーシステムで既に導入されている場合のみ検討し、新規プロジェクトでは express-rate-limit や rate-limiter-flexible の評価を推奨します。
express-rate-limit 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.
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)
The rate limiter comes with a built-in memory store, and supports a variety of external data stores.
All function options may be async. Click the name for additional info and default values.
| Option | Type | Remarks |
|---|---|---|
windowMs | number | How long to remember requests for, in milliseconds. |
limit | number | function | How many requests to allow. |
message | string | json | function | Response to return after limit is reached. |
statusCode | number | HTTP status code after limit is reached (default is 429). |
handler | function | Function to run after limit is reached (overrides message and statusCode settings, if set). |
legacyHeaders | boolean | Enable the X-Rate-Limit header. |
standardHeaders | 'draft-6' | 'draft-7' | 'draft-8' | Enable the Ratelimit header. |
identifier | string | function | Name associated with the quota policy enforced by this rate limiter. |
store | Store | Use a custom store to share hit counts across multiple nodes. |
passOnStoreError | boolean | Allow (true) or block (false, default) traffic if the store becomes unavailable. |
keyGenerator | function | Identify users (defaults to IP address). |
ipv6Subnet | number (32-64) | function | false | How many bits of IPv6 addresses to use in default keyGenerator |
requestPropertyName | string | Add rate limit info to the req object. |
skip | function | Return true to bypass the limiter for the given request. |
skipSuccessfulRequests | boolean | Uncount 1xx/2xx/3xx responses. |
skipFailedRequests | boolean | Uncount 4xx/5xx responses. |
requestWasSuccessful | function | Used by skipSuccessfulRequests and skipFailedRequests. |
validate | boolean | object | Enable or disable built-in validation checks. |
Sponsored by Zuplo a fully-managed API Gateway for developers. Add dynamic rate-limiting, authentication and more to any API in minutes. Learn more at zuplo.com
Thanks to Mintlify for hosting the documentation at express-rate-limit.mintlify.app
Finally, thank you to everyone who's contributed to this project in any way! 🫶
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!
MIT © Nathan Friedly, Vedant K