req-guard-lite:一款以TypeScript为优先的Express简易速率限制器。
了解轻量级、无依赖的Express速率限制器的工作原理,从内存默认配置到Redis扩展及自定义键生成器。
每个 Express 应用最终都会遇到需要实施速率限制的情况。
无论目的是保护登录接口、减少垃圾信息,还是防止意外过度使用,一旦 API 对外公开,限制进入的流量就立刻从可选功能变成了必要要求。
在寻找能满足常见需求的速率限制方案时,我们发现尽管已有许多功能强大的库,但很多项目其实只是希望拥有一个体积小、易于理解且便于定制的工具。
正是这一需求缺口促使我们开发了 req-guard-lite。
为何还要另一个速率限制器?
大多数 API 从一开始就不需要完整的企业级安全套件。
通常,人们只需要能够编写类似这样的代码即可:
app.use(rateLimit({
max: 100,
windowMs: 15 * 60 * 1000
}));
…然后继续构建应用程序的其余部分。
该模块的设计目标如下:
- 保持轻量级
- 易于设置
- 从一开始就以 TypeScript 为开发基础
- 便于扩展
- 既适用于小型项目,也适用于大规模系统
介绍 req-guard-lite
req-guard-lite 是一款精简的 Express 中间件,旨在保护您的 API 免受海量请求的冲击。
默认情况下它完全在内存中运行,但也可以通过接入 Redis 实现向分布式架构的扩展。
它的功能范围被刻意限定——只专注做好一件事:
记录传入的请求,一旦超过设定的限制就拒绝相应客户端。
功能特性
轻量级实现 核心包无任何运行时依赖 可作为 Express 中间件使用 原生支持 TypeScript 可集成 Redis 支持自定义存储后端 支持自定义键生成器
快速入门
将其作为 Express 的配套组件进行安装。
npm install req-guard-lite express
接下来,将其接入您的应用中。
import express from 'express';
import { rateLimit } from 'req-guard-lite';
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests, please try again later.'
});
app.use(limiter);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
这就是全部的设置流程。
现在,您的 API 会阻止任何客户端在15分钟内发送超过100次请求。
默认内存存储
开箱即用,请求计数器存储在内存中。
实际意义如下:
- 无需 Redis
- 无需数据库
- 无需额外配置
- 非常适合本地开发
- 单服务器生产环境中的理想选择
对于大多数应用而言,这已经是最为必要的限流方式了。
利用 Redis 实现扩展
当应用需要部署在多台服务器或容器上运行时,这些实例就需要一个共享的请求计数视图。
这就是 Redis 在此发挥的作用。
import Redis from "ioredis";
import { createRedisStore } from "req-guard-lite/redis";
const redis = new Redis();
const limiter = rateLimit({
max: 100,
windowMs: 15 * 60 * 1000,
store: createRedisStore(redis, {
max: 100,
windowMs: 15 * 60 * 1000
})
});
通过这种方式,每个服务器实例都会读取和写入相同的计数器,因此无论哪个节点处理请求,限流规则都能保持一致。
自定义键生成器
按 IP 地址进行限流并不总是最佳方案。
在某些情况下,你可能更希望根据以下内容设置限流:
- 用户 ID
- API 密钥
- 租户标识符
- 组织单位
- JWT 主体声明
- 或任何符合你业务模型的其他标识符
为支持这一点,req-guard-lite允许你提供自己的密钥生成函数。
const limiter = rateLimit({
max: 100,
keyGenerator: (req) =>
req.headers["x-api-key"] as string
});
或者,以已登录用户作为密钥:
const limiter = rateLimit({
max: 50,
keyGenerator: (req) =>
(req as any).user.id
});
该中间件本身并不关心密钥代表什么——它只是根据你的函数返回的任何标识符来计数。
自行实现存储机制
从一开始,可扩展性就是核心要求之一。
req-guard-lite并未强制你使用Redis,而是提供了一个简单的RateLimitStore接口。如果你的基础设施已经依赖:
- PostgreSQL
- DynamoDB
- Memcached
- MongoDB
- SQLite
- 其他自定义缓存层
你只需实现该接口即可将其集成进来。
class MyStore implements RateLimitStore {
consume(key: string) {
// your implementation
}
}
这种设计使得该包能够适配您正在使用的几乎所有后端系统。
一个重要的生产环境建议
如果您的应用运行在以下设备之后:
- Nginx
- AWS负载均衡器
- Heroku
- Cloudflare
- 任何反向代理服务器
请务必正确配置Express:
app.set("trust proxy", 1);
如果跳过这一步,Express往往会将每个传入的请求都视为来自代理服务器本身,这意味着所有用户最终都会共享同一个速率限制阈值。这只是一个简单的修改,却能避免许多团队在生产环境中遇到的麻烦。
工作原理
其内部流程被刻意设计得极为简单:
- 有请求传入。
- 中间件会为该请求生成一个键值(默认为客户端IP地址)。
- 当前使用的存储机制会递增与该键值关联的计数器。
由于该存储系统支持插件化,无论背后使用的是内存、Redis还是自定义实现,都遵循相同的处理流程。
为何选择 TypeScript?
整个库均用 TypeScript 编写,由此带来以下优势:
- 全程严格的类型控制
- 更完善的编辑器自动补全功能
- 更简单的长期维护
- 更难被误用的 API 接口
使用 TypeScript 的用户可以直接获得完整的类型定义,无需额外安装 @types 包。
发展路线图
开发工作仍在持续,已有若干新功能正在规划中。
v0.4.0
- 支持标准的速率限制响应头
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
这些响应头能让客户端应用程序了解在达到限制之前还剩多少请求。
v0.5.0
当超过限制时会触发可配置的钩子函数,适用于以下场景:
- 日志记录
- 指标收集
- 警报发送
- 数据分析
- 将数据发送到外部监控工具
为何选择开源
这个项目的诞生并非因为现有库存在不足——Node 生态系统中已经有了几款出色的限流解决方案。req-guard-lite的出现是因为其目标是打造这样一个包:
- 简洁到可以一次性读懂
- 易于扩展
- 以 TypeScript 为设计基础
- 没有不必要的复杂性
- 足够灵活,能随着实际生产需求进行扩展
开发这个项目也是一次宝贵的实践机会,让我学会了如何打包发布、设计API、编写测试代码、与Redis集成,以及创建便于其他开发者使用的抽象层。
结语
将项目开源是提升工程技能最有效的方法之一。一旦其他开发者能够安装、使用并为你贡献代码,你就不得不考虑超出自身直接需求之外的问题——文档编写、API设计、测试、版本控制以及向后兼容性都成了你必须解决的现实约束。
req-guard-lite最初只是为满足个人需求而开发的一个小型中间件,但希望它能发展成一款对其他Express开发者来说实用、轻量且可扩展的限流工具。欢迎任何反馈、功能建议及代码贡献。
相关阅读
- 在 React 前端与 Node 后端之间共享同一个 Zod 模式 — 了解如何利用单一的 Zod 模式对 React 表单、API 响应、Express 请求体以及环境变量进行验证,同时生成对应的 TypeScript 类型。
- Zod 与 express-validator:两种 Express 验证方案 — 对比了基于 Zod 的模式优先请求验证方式与基于链式的 express-validator 中间件,涵盖了配置方法、错误格式化以及常见问题。