· rate-limiting / redis / nodejs
Thư viện rate-limit tốt nhất cho Node.js + Redis 2026
rate-limiter-flexible là mặc định cho mọi Node API chạy Redis. @upstash/ratelimit là lựa chọn duy nhất trên serverless edge. Đây là cách chọn.
Bởi Ethan
2.169 từ · 11 phút đọc
Dùng rate-limiter-flexible v11.1.0 cho mọi Node.js API chạy trên hạ tầng riêng. Dùng @upstash/ratelimit v2.0.8 nếu bạn deploy lên Vercel Edge Functions, Cloudflare Workers, hoặc AWS Lambda. Nếu team bạn đang dùng express-rate-limit, chỉ cần thêm rate-limit-redis v5.0.0 làm Redis store — năm dòng code, không thêm abstraction nào mới.
Bài này dành cho ai
Node.js developer cần rate limiting phân tán: nhiều instance ứng dụng đằng sau load balancer, hoặc serverless function nơi counter trong bộ nhớ của từng process không có ý nghĩa gì. Nếu bạn chỉ chạy một process và việc reset counter khi restart không ảnh hưởng đến nghiệp vụ, thì in-memory store là đủ và bài viết này không dành cho bạn.
Những gì chúng tôi đã thử nghiệm
Tất cả version và lượt download được ghi nhận tại ngày 2026-06-01:
rate-limiter-flexiblev11.1.0 — ~1.2–2M lượt download/tuần, 3.530 GitHub stars, ISCexpress-rate-limitv8.5.2 +rate-limit-redisv5.0.0 — ERL: ~24–38M lượt download/tuần (bao gồm tất cả người dùng, không chỉ người dùng Redis), MIT@upstash/ratelimitv2.0.8 — ~357K lượt download/tuần, MITbottleneckv2.19.5 — được xem xét cho đầy đủ; không khuyến nghị cho dự án mới- DIY
ioredisv5 + Lua scripts — lối thoát không phụ thuộc thư viện nào
Không có benchmark độc lập nào so sánh cả bốn thư viện tại thời điểm viết bài. Một bài đăng trên DEV Community đề cập đến mức tăng tốc 9× của một cách triển khai tùy chỉnh, nhưng phương pháp không được tái hiện ở bất kỳ đâu. Chúng tôi ghi nhận sự vắng mặt đó thay vì bịa ra con số.
Tại sao dùng Redis
Counter trong process của instance A không biết gì về các request đến instance B. Đặt hai Node.js server đằng sau load balancer và giới hạn rate của bạn tự nhân đôi — đó là kiểu “miễn phí” sai chỗ.
Redis giải quyết vấn đề này bằng ba cơ chế. Shared state: mọi instance đều đọc và ghi cùng một counter. Lua script nguyên tử: Redis thực thi Lua single-threaded, nên hai process đồng thời không thể cùng đọc count=4 và cùng tăng lên 5. Dọn dẹp theo TTL: key với EXPIRE tự biến mất, không cần cron. Cách dùng Lua EVAL hiệu quả hơn retry WATCH/MULTI/EXEC khi contention cao vì chỉ cần một round trip và hỗ trợ conditional logic; tutorial rate-limiting trên Redis.io khuyến nghị cách này.
Nếu bạn đang cân nhắc giữa Redis và fork mã nguồn mở của nó, xem Redis vs Valkey 2026 để so sánh về giấy phép, chi phí và cluster.
Các thư viện
rate-limiter-flexible — thư viện đầy đủ nhất
Không phụ thuộc framework. Hoạt động với bất kỳ HTTP framework nào, hoặc không cần framework. Dùng Lua script bên trong nên cluster-safe ngay từ đầu. Hỗ trợ ioredis (mặc định) và node-redis v4+ (cần useRedisPackage: true — nếu không có flag này, thư viện sẽ coi như đang dùng ioredis). Cũng hỗ trợ MongoDB, DynamoDB, PostgreSQL và Memcached mà không cần thay đổi API — tiện khi muốn graceful degradation lúc Redis gặp sự cố.
Breaking change v10.0.0 (tháng 3 năm 2026): points và duration bây giờ là bắt buộc, không có giá trị mặc định. Nâng cấp từ v9.x mà không thêm giá trị tường minh sẽ báo lỗi ngay khi khởi động.
Lưu ý về node-redis v4 cluster: v4.7.0 có hai lỗi cluster mode đã được sửa trong v5 — race condition trong cluster-slots discovery và lỗi xử lý error trong createCluster. Với Redis Cluster, dùng ioredis v5 hoặc node-redis v5+.
Tài liệu chính thức báo cáo 0.7ms trung bình ở Node.js Cluster mode và 2.5ms ở distributed mode. Đây là benchmark tự báo cáo; coi đó là ngưỡng dưới, không phải đảm bảo.
// ioredis setup (recommended for cluster)
const Redis = require('ioredis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
const redisClient = new Redis({ enableOfflineQueue: false });
const rateLimiter = new RateLimiterRedis({
storeClient: redisClient,
keyPrefix: 'rl',
points: 100, // requests allowed
duration: 60, // per 60 seconds
blockDuration: 600, // block 10 min after limit exceeded
});
module.exports = async function rateLimitMiddleware(req, res, next) {
try {
const result = await rateLimiter.consume(req.ip);
res.set({
'X-RateLimit-Limit': 100,
'X-RateLimit-Remaining': result.remainingPoints,
'X-RateLimit-Reset': new Date(Date.now() + result.msBeforeNext).toISOString(),
});
next();
} catch (rejRes) {
res.set('Retry-After', String(Math.ceil(rejRes.msBeforeNext / 1000)));
res.status(429).json({ error: 'Too Many Requests' });
}
};
Nếu bạn đang dùng node-redis v4+, thêm useRedisPackage: true vào constructor options và thay new Redis() bằng await createClient({ enable_offline_queue: false }).connect().
express-rate-limit + rate-limit-redis — lựa chọn mặc định cho Express
Rate limiter phổ biến nhất trong hệ sinh thái Express, được tích hợp mặc định trong hầu hết scaffolding và boilerplate. Mặc định dùng MemoryStore in-memory trừ khi bạn thay bằng store khác; [email protected] là Redis store chính thức.
Thuật toán chỉ là fixed window — không có sliding window, không có token bucket. Điều này ổn với hầu hết các API thông thường, nhưng không đủ cho auth endpoint nơi tấn công boundary-burst là vấn đề thực.
Con số 24–38M lượt download/tuần là của express-rate-limit nói chung. Phần lớn trong số đó dùng in-memory store. Các cài đặt sử dụng Redis chỉ là một phần nhỏ.
[email protected] yêu cầu express-rate-limit >= 8.5.0. Kiểm tra version ERL trước khi nâng cấp.
import express from 'express';
import { rateLimit } from 'express-rate-limit';
import { RedisStore } from 'rate-limit-redis';
import { createClient } from 'redis';
const app = express();
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15-minute window
limit: 100,
standardHeaders: 'draft-8',
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args: string[]) => redisClient.sendCommand(args),
}),
handler: (req, res) => {
res.status(429).json({ error: 'Too many requests, try again later.' });
},
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
limit: 5,
store: new RedisStore({
prefix: 'rl:auth:',
sendCommand: (...args: string[]) => redisClient.sendCommand(args),
}),
});
app.use('/api/', apiLimiter);
app.use('/auth/login', authLimiter);
@upstash/ratelimit — tùy chọn duy nhất tương thích serverless
Mọi thư viện khác trong danh sách này đều cần kết nối TCP liên tục tới Redis. Cloudflare Workers, Vercel Edge Functions và AWS Lambda không hỗ trợ persistent connection theo cách một server truyền thống làm được. @upstash/ratelimit dùng Upstash Redis, truy cập Redis qua HTTP (REST). Không cần kết nối liên tục — thư viện hoạt động giống nhau trên Node.js, Deno, Bun và V8 isolate.
Độ phủ thuật toán tốt nhất trong danh sách: fixed window, sliding window (trung bình có trọng số liên tục, loại bỏ boundary burst), và token bucket. Peer dependency là @upstash/redis@^1.34.3 — bạn không thể trỏ nó đến Redis tự host hoặc Redis Cloud.
Gói miễn phí của Upstash bao gồm 500.000 lệnh/tháng, đủ cho môi trường phát triển và ứng dụng production nhỏ. Tích hợp Vercel + Upstash chỉ cần một click từ Vercel marketplace. Nếu ứng dụng serverless của bạn còn cần xử lý background job, QStash của Upstash được đề cập trong Thư viện job queue tốt nhất cho Node.js và TypeScript.
// middleware.ts (Next.js 14+ / Vercel Edge)
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
import { NextRequest, NextResponse } from "next/server";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(), // UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN
limiter: Ratelimit.slidingWindow(100, "1 m"), // 100 req/min, sliding
analytics: true,
prefix: "app:ratelimit",
});
export const config = { matcher: "/api/:path*" };
export async function middleware(request: NextRequest) {
const forwarded = request.headers.get("x-forwarded-for");
const ip = forwarded ? forwarded.split(",")[0].trim() : request.ip ?? "anonymous";
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse(
JSON.stringify({ error: "Rate limit exceeded" }),
{
status: 429,
headers: {
"Content-Type": "application/json",
"X-RateLimit-Limit": String(limit),
"X-RateLimit-Remaining": "0",
"X-RateLimit-Reset": new Date(reset).toISOString(),
},
}
);
}
const response = NextResponse.next();
response.headers.set("X-RateLimit-Limit", String(limit));
response.headers.set("X-RateLimit-Remaining", String(remaining));
return response;
}
Đặt UPSTASH_REDIS_REST_URL và UPSTASH_REDIS_REST_TOKEN trong biến môi trường Vercel — Upstash tự inject nếu bạn kết nối integration.
DIY ioredis + Lua scripts
Khi bạn cần một thuật toán mà không thư viện nào ở trên triển khai, hoặc khi việc thêm dependency bị hạn chế bởi yêu cầu compliance. Bạn sở hữu Lua scripts; bạn cũng sở hữu các lỗi.
Tutorial rate-limiting chính thức của Redis cung cấp scripts sẵn sàng cho production với cả năm thuật toán: fixed window, sliding window log, sliding window counter, token bucket và leaky bucket. Sliding window counter là lựa chọn họ khuyến nghị — ít tốn bộ nhớ, độ chính xác gần như tuyệt đối, cluster-safe qua hash tag.
bottleneck — không khuyến nghị
bottleneck v2.19.5 được phát hành năm 2019. Commit cuối: 2020. Một GitHub issue từ tháng 12 năm 2022 hỏi về bảo trì không nhận được phản hồi chính thức nào. Đây chủ yếu là outbound rate limiter — throttle các request code của bạn gửi đến external service — không phải inbound API rate limiter. ~3–10M lượt download/tuần phản ánh việc sử dụng legacy. Đừng bắt đầu dự án mới với nó.
So sánh
| Thư viện | Phiên bản | DL/tuần | Thuật toán | Types TS | Cluster-safe | Framework | Redis client |
|---|---|---|---|---|---|---|---|
rate-limiter-flexible | 11.1.0 | ~1.2–2M | Fixed window + burst | Có sẵn | Có (Lua) | Agnostic | ioredis v5, node-redis v5 |
express-rate-limit + rate-limit-redis | 8.5.2 + 5.0.0 | ~24–38M (ERL tổng) | Fixed window | Có sẵn | Có | Chỉ Express | node-redis, ioredis, và các client khác |
@upstash/ratelimit | 2.0.8 | ~357K | Fixed, sliding, token bucket | Có sẵn | Có (HTTP) | Agnostic | Chỉ @upstash/redis |
| DIY ioredis + Lua | — | — | Tùy ý | N/A | Có | Agnostic | ioredis v5 |
bottleneck | 2.19.5 | ~3–10M | Token bucket (outbound) | Có sẵn | Có | Agnostic | ioredis (datastore) |
Lựa chọn theo use case
Serverless / edge (Vercel, Cloudflare Workers, Lambda) → @upstash/ratelimit v2.0.8. Đây là lựa chọn duy nhất không cần persistent connection. Mọi thư viện khác đều cần TCP socket liên tục, thứ mà serverless runtime không hỗ trợ đáng tin cậy. Bắt đầu với gói miễn phí của Upstash — 500.000 lệnh/tháng trước khi tốn một đồng nào.
Monolith hoặc microservices REST API → rate-limiter-flexible v11.1.0. Không phụ thuộc framework, cluster-safe, được duy trì tích cực, và có blockDuration cho chiến lược chặn DoS thực sự (không chỉ rate limiting). Latency 0.7ms ở cluster mode nằm trong ngưỡng chấp nhận được của hầu hết mọi API.
Express app đang dùng express-rate-limit → thêm rate-limit-redis v5.0.0. Hai lệnh install, năm dòng code, không thay đổi API. Giới hạn của fixed window chấp nhận được cho bảo vệ API thông thường; thêm authLimiter nghiêm ngặt hơn với window dài hơn cho các endpoint đăng nhập.
Kết luận
rate-limiter-flexible v11.1.0 là lựa chọn mặc định cho mọi Node.js API có Redis instance thực sự. Không phụ thuộc framework, được duy trì tích cực, cluster-safe, hỗ trợ nhiều Redis client, và có chiến lược block vượt ra ngoài rate counting cơ bản. Với serverless và edge, @upstash/ratelimit không phải sự đánh đổi — đó là lựa chọn duy nhất hoạt động được.
Lưu ý
@upstash/ratelimitv2.0.8 không có release nào trong khoảng 5 tháng tính đến tháng 6 năm 2026. Chức năng cốt lõi ổn định, nhưng hãy theo dõi GitHub repo để nắm tín hiệu bảo trì.- Số liệu benchmark của
rate-limiter-flexible(0.7ms, 2.5ms) đến từ tài liệu của chính thư viện, không phải kiểm thử độc lập. Dùng làm tham chiếu thôi, không phải đảm bảo cho topology của bạn. - Con số download của express-rate-limit bị thổi phồng — phần lớn cài đặt dùng in-memory store. Đừng so sánh với lượt download của
rate-limiter-flexiblenhư thể chúng đại diện cho cùng use case. - Bài viết này có affiliate link đến Upstash. Upstash thực sự là lựa chọn đúng cho serverless rate limiting và chúng tôi sẽ khuyến nghị nó ngay cả khi không có quan hệ affiliate. Nếu điều đó thay đổi, khuyến nghị sẽ thay đổi trước.
Tham khảo
| Nguồn | Link |
|---|---|
| rate-limiter-flexible — GitHub | github.com/animir/node-rate-limiter-flexible |
| rate-limiter-flexible — Redis wiki | wiki/Redis |
| rate-limiter-flexible — releases | releases |
| rate-limiter-flexible — npm | npmjs.com/package/rate-limiter-flexible |
| express-rate-limit — npm | npmjs.com/package/express-rate-limit |
| rate-limit-redis — README | github.com/express-rate-limit/rate-limit-redis |
| @upstash/ratelimit — GitHub | github.com/upstash/ratelimit-js |
| @upstash/ratelimit — tài liệu thuật toán | upstash.com/docs/…/algorithms |
| @upstash/ratelimit — bắt đầu nhanh | upstash.com/docs/…/gettingstarted |
| @upstash/ratelimit — npm | npmjs.com/package/@upstash/ratelimit |
| bottleneck — npm | npmjs.com/package/bottleneck |
| bottleneck — issue bảo trì | github.com/SGrondin/bottleneck/issues/207 |
| Tutorial rate-limiting của Redis | redis.io/tutorials/howtos/ratelimiting |
| node-redis — race condition cluster-slots (issue #2704) | github.com/redis/node-redis/issues/2704 |
| node-redis — lỗi xử lý createCluster (issue #2721) | github.com/redis/node-redis/issues/2721 |