2026年的Express与Fastify:实用的Node.js框架对比
本指南从性能、验证机制、生态系统及错误处理等方面对比了Express与Fastify,并介绍了Express 5中的主要变更点。
想象一个团队正在搭建一个新的 Node.js 后端。
他们开始研究各种框架,很快就有两个名称成为讨论的焦点:
Express.js 和 Fastify。
接着基准测试结果出现了。
在众多模拟测试中,Fastify 的吞吐量明显更高。
人们自然而然会想到:
“如果 Fastify 在速度上更优,那还有人会继续使用 Express 吗?”
表面上看这确实是个合理的问题。
但实际上选择后端框架从来不会只取决于某一项指标。
每秒原始请求数只是其中的一个因素。你还需考虑整个生态系统、中间件的工作方式、内置的验证功能、对 TypeScript 的支持程度、迁移的成本、开发人员的日常使用体验、你正在维护的现有代码库,以及你的具体应用实际的需求。
这种比较还有第二层值得注意的方面。
Express 5现已正式发布。
在长期使用 Express 4之后,这个新版本带来了一些变化,任何维护旧版 Express 代码库的人都需要了解这些变化。
在了解了这些背景之后,让我们深入探讨究竟是什么让 Express 与 Fastify有所区别——更重要的是,了解在何种情况下使用其中哪一个更为合适。
首先:Express 和 Fastify 到底是什么?
这两种框架的存在都是为了帮助你在 Node.js 上构建 Web 服务器。
从根本上说,它们通过提供更简洁的 API 来定义路由和处理请求,从而让你无需直接操作 Node 的底层 HTTP 模块。
以下是一个最简单的 Express 服务器示例:
const express = require("express");
const app = express();
app.get("/users", (req, res) => {
res.json([
{ id: 1, name: "Neha" },
{ id: 2, name: "Rahul" }
]);
});
app.listen(3000);
Fastify 的起始结构也颇为相似:
const fastify = require("fastify")({
logger: true
});
fastify.get("/users", async (request, reply) => {
return [
{ id: 1, name: "Neha" },
{ id: 2, name: "Rahul" }
];
});
fastify.listen({ port: 3000 });
注意到什么了吗?
这两个示例都并不令人望而生畏。
只有当你的应用发展超出这个简单示例阶段时,两者之间的真正差异才会显现出来。
Express 与 Fastify:整体对比
让我们来看看为什么这两个框架之间的差异在实践中其实很重要。
1. 性能:Fastify 具有优势
这很可能是 Fastify 在这类讨论中频繁被提及的最主要原因。
从一开始,性能与极低的开销就是 Fastify 的核心设计目标。
其内部机制在验证传入数据及序列化输出响应时严重依赖模式,这能够显著提升处理大量 API 请求时的吞吐量。Fastify 的官方文档建议专门使用 JSON Schema 进行路由验证和响应序列化。
不过,这里有一个重要的注意事项。
不要仅凭某一项基准测试结果就直接得出以下结论:
使用 Fastify 就意味着应用程序的速度会快三倍。
大多数框架的基准测试都是在严格控制的条件下,衡量框架本身的开销而已。
Fastify 的维护者自己也承认,他们发布的基准测试只是模拟的“Hello World”风格测试,如果性能对您而言至关重要,他们明确建议自行对实际应用进行基准测试。
设想这样一个请求流程:
Request
↓
Authentication
↓
Database query
↓
Redis
↓
External API
↓
Business logic
↓
Response
如果仅数据库查询就需要 80 毫秒,即便减少少量框架开销,也不会显著提升该接口的速度。
因此您真正应该问自己的问题是:
我的应用是否真的受到 CPU 或框架开销的瓶颈限制?
换言之:是框架本身导致了请求处理速度变慢吗?如果答案是肯定的,那么选择 Fastify 就更有理由了。
如果答案是否定的,那么通用的框架基准测试很可能不应成为您的决策依据。
2. 验证:这是 Fastify 变得有趣的地方
假设你的 API 被设计为接收如下格式的负载数据:
{
"name": "Neha",
"age": 25
}
显然,你会希望拒绝那些格式错误的负载数据,比如:
{
"name": 123,
"age": "hello"
}
在 Express 中,团队通常会借助额外的库来处理这类请求验证。而 Fastify 采取了不同的方法:基于模式的验证直接内嵌在框架本身中。
实际应用中的样子如下:
const schema = {
body: {
type: "object",
required: ["name", "age"],
properties: {
name: { type: "string" },
age: { type: "integer" }
}
}
};
fastify.post("/users", { schema }, async (request, reply) => {
return { message: "User created" };
});
Fastify 允许你为请求和响应周期中的不同部分定义 JSON Schema 定义,包括:
- 请求体
- 查询参数
- 路由参数
- 请求头
- 响应序列化
在底层实现上,Fastify依赖Ajv来提供这一验证功能,同样的模式定义也可用于加快响应的序列化速度。
Ajv是“Another JSON Schema Validator”的缩写,是一个快速且符合标准的库,可用于根据JSON Schema定义对JavaScript数据对象进行验证,同时可在Node.js和浏览器环境中使用。
当你的API需要处理大量结构化的输入与输出时,这种以模式定义为主的方法就会显得尤为有用。
3. Express拥有Fastify难以替代的优势:其生态系统
Express自2010年问世以来,已经积累了大量相关的知识、工具以及社区经验。
- 需要身份验证功能?有相应的插件可用。
- 需要日志记录功能?也有对应的插件可用。
这种生态系统的深度比乍看之下更为重要。
想象一下,你加入了一家后端系统已运行六年的公司。你无法从头选择框架,只能使用现有的架构,它可能看起来像这样:
Express
├── 200+ routes
├── authentication middleware
├── custom middleware
├── logging
├── validation
├── monitoring
└── lots of business logic
仅仅因为 Fastify 的性能测试结果更好就重写所有代码,真的合理吗?大概不合理。
迁移现有代码库总是需要付出代价,任何实际的工程决策都需要权衡这一成本与预期收益。
4. 中间件与插件
这两种框架在架构层面也存在更深层次的差异。
Express 是以中间件概念为基础构建的。典型的配置可能如下所示:
app.use(authMiddleware);
app.use(loggingMiddleware);
app.use(express.json());
每个传入的请求会依次经过这些中间件函数的处理。
相比之下,Fastify 采用基于插件和封装的架构。从概念上来看,其处理流程更类似于这样:
Request
↓
Fastify
↓
Hooks
↓
Plugins
↓
Route
↓
Response
对于从一开始就按照 Fastify 的模型设计的应用程序,这种结构能让更庞大的代码库更易于组织和理解。
不过这也存在一定的权衡。如果你多年来一直习惯使用 Express 风格的中间件,那么最初适应 Fastify 的插件和钩子系统可能会觉得不太熟悉。
5. 错误处理
在 Express 4 中,处理异步路由处理程序产生的错误通常需要手动转发。典型的实现方式如下:
app.get("/user/:id", async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
res.json(user);
} catch (error) {
next(error);
}
});
Express 5 大大简化了这一过程。现在,路由处理程序或中间件抛出的拒绝承诺会自动传递给错误处理中间件,因此你可以写出更简洁的代码:
app.get("/user/:id", async (req, res) => {
const user = await getUserById(req.params.id);
res.json(user);
});
如果 getUserById() 抛出异常或被拒绝,Express 5 会自动捕获并将其路由到错误处理函数,无需显式的 try/catch 或 next(err) 调用。
单看之下,这似乎只是微小的语法便利。但在包含数百个路由的代码库中,这类小小的优化会让代码更加整洁,也更容易避免错误。
Express 5:究竟发生了什么变化?
Express 5于2024年10月发布,因此进入2026年后它已不再是最新版本。尽管如此,仍有大量生产环境中的应用程序运行在Express 4上,这意味着对于任何计划升级的人来说,了解各版本之间的变化至关重要。
确实存在一些需要注意的破坏性变更。
1. 可选路由参数已更改
在Express 4中,你可能会这样编写路由:
app.get("/:file.:ext?", handler);
Express 5则用以下方式替代该写法:
app.get("/:file{.:ext}", handler);
用于标记参数为可选的旧版?符号已不再有效。
为何要做出这种改变?
新语法能让人们一目了然地看出路径中的哪部分是可选的。
从表面上看这只是细微改动,但可能会悄悄破坏大型成熟应用程序中的数十个路由。
2. 通配符路由已更改
此前,通配符路由可能如下所示:
app.get("/*", handler);
Express 5 要求为通配符指定名称:
app.get("/*splat", handler);
如果需要该通配符同时匹配根路径 /,则需这样包裹它:
app.get("/{*splat}", handler);
这又是另一个细微的语法变化,可能在升级过程中悄无声息地破坏现有的路由逻辑。
3. 正则表达式路由模式已更改
Express 5 不再支持那些依赖正则表达式字符的旧字符串模式。
例如,现在通常需要传递一个路径数组,而非将多个路径片段直接嵌入到单个路径字符串中:
app.get(
["/discussion/:slug", "/page/:slug"],
handler
);
这一变化的目的是让路由匹配更加明确且可预测,而非依赖类似正则表达式的快捷方式。
4. req.body 现在可能为 undefined
这是一个很容易被忽视的细微问题。
在 Express 4 中,人们通常认为:
req.body
即使在任何解析发生之前,它也会默认为空对象。
在 Express 5 中,如果没有任何内容被解析,req.body 就会直接为 undefined。
这意味着根据路由的不同,就需要进行这样的防御性检查:
if (!req.body) {
return res.status(400).send("Request body required");
}
这只是个细微的行为变化,但正是这类小细节在升级后可能导致难以追踪的错误。
5. express.urlencoded() 发生变化
extended 选项的默认值现在为 false。
因此,不必依赖隐含的默认值:
app.use(express.urlencoded());
如果你的应用依赖旧的行为模式,就需要显式设置该选项:
app.use(
express.urlencoded({
extended: true
})
);
在迁移现有代码库时,还有另一个需要仔细审核的细节。
6. 请求体解析器的变化
Express 5还优化了请求体解析的内部机制。
现在你可以这样编写:
app.use(express.json());
app.use(
express.urlencoded({
extended: false
})
);
而无需像以前那样将多个独立的body-parser中间件包拼接在一起。
此外,Express 5还支持对请求体进行Brotli压缩,并允许你配置URL编码数据包的最大深度。
7. 一些旧的响应API已被移除
想象一下某个较旧版本的Express应用中可能存在如下代码:
res.send({
message: "Success"
}, 200);
在Express 5中,正确的格式应为:
res
.status(200)
.send({
message: "Success"
});
同样地,旧的简写方式:
res.redirect("back");
也已被完全移除。
官方迁移指南建议手动查看引用头信息,若缺失则使用默认路径。
这些单独的修改其实都不难解决。
但试想一下,如果有一个遗留代码库,其中包含数千行用旧方式编写的调用代码。
在如此规模的情况下,升级就不再只是简单的依赖项更新,而会变成一项独立的重大工程任务。
那么,Express还是Fastify:哪个更胜一筹?
现在你已经有足够的背景信息来做出实际决策了。
我不建议仅基于以下标准来做决定:
“哪个的测试速度更快?”
相反,应先从应用程序本身入手进行分析。
继续使用Express的理由:
- 你才刚刚开始学习Node.js的后端开发。
- 你的团队已经熟悉Express的使用方式。
选择 Fastify 的理由:
- 你正在从零开始构建一个以 API 为核心的服务。
- 吞吐量以及降低框架开销确实很重要。
- 你希望由架构模式直接驱动数据验证。
- 你也希望通过架构模式来处理序列化操作。
- 你正在开发微服务。
- 你喜欢基于插件的架构方式。
- 你愿意采用相对较新的生态系统。
即使是为大型系统选择 Express,也并非有什么本质上的问题。
仅仅因为是“大型应用”,并不代表 Fastify 就一定是最佳选择。
框架之外的整体架构通常比框架本身的选择更为重要。
一种基本的分析思路
以下是做出决策时的大致思考流程:
Start
│
▼
Is this an existing app?
/ \
Yes No
│ │
▼ ▼
Already using Need very high
Express? throughput?
/ \ / \
Yes No Yes No
│ │ │ │
▼ ▼ ▼ ▼
Keep Evaluate Fastify Evaluate
Express migration both
在确定最终答案之前,还有一个问题值得思考:
我实际上想要解决的是什么问题?
如果现有的 Express 应用运行缓慢,先不要急于将责任归咎于框架本身。
先对其性能进行分析。
真正的症结可能在于:
Slow API
↓
Database query
↓
Missing index
或者:
Slow API
↓
External API
↓
3-second response time
又或者:
Slow API
↓
Expensive business logic
↓
CPU bottleneck
仅仅更换框架并不能解决这些根本问题。
我的看法
如果今天你要启动一个新的小型 Node.js 项目,仅仅因为 Fastify 的基准测试成绩更好就放弃使用 Express,这是不明智的。Express 拥有庞大的生态系统、直观的设计理念,以及多年来积累的丰富经验。
不过,Fastify 也不应被忽视。对于那些对吞吐量、数据结构验证、序列化处理以及尽可能低的框架开销有较高要求的新服务来说,Fastify 确实值得认真考虑。
而如果你继承了一个现有的 Express 4 应用程序?仅仅因为 Fastify 更快就重新编写它,这是错误的做法。
更好的方法是先了解该应用,找出真正的瓶颈所在,验证其与Express 5的兼容性,完成迁移测试,之后再判断更换框架是否真的能带来足够的价值以弥补相应的努力。
这大概就是这里的核心要点。
基准测试表现最好的框架并不一定适合你的情况。
合适的框架应是那个既能解决你的实际问题,又能尽量减少不必要的复杂性的框架。
相关阅读
- Node.js、Deno与Bun对比:基准测试、优缺点及迁移策略 — 阐述了Node.js、Deno与Bun在架构上的实际差异,2025年的基准测试结果揭示了什么,以及如何决定何时进行迁移。
- 在React前端与Node后端之间共享同一个Zod Schema — 了解如何利用单个Zod Schema来验证React表单、API响应、Express请求体以及环境变量,同时生成对应的TypeScript类型。