Web 框架对照:Express / Fastify / NestJS 怎么选
前面第 6 章讲了 Express,第 7 章讲了 NestJS,但你可能还是没底:到底该用哪个?Fastify 是不是"更快的 Express"?NestJS 是不是"过度设计"?这一章不讲用法,只讲选型——性能差异从哪来、三者的设计哲学差在哪、什么规模的项目配什么框架。选错框架不致命,但会把后面两年的开发体验拉低一个档次。
论三个框架其实在回答三个不同的问题
Express 回答的是:"我怎么用最少的概念写一个 HTTP 服务?"它的全部心智只有"中间件"一个概念,路由、错误处理、第三方库全靠中间件拼出来。代价是没有约束——100 个人写 Express 会写出 100 种结构。
Fastify 回答的是:"我要性能,还要接口契约。"它把"请求/响应的 JSON Schema"提到一等公民的位置,靠 schema 同时做校验 + 序列化加速 + 文档生成。它比 Express 快,主要就快在 schema 驱动的序列化上。
NestJS 回答的是:"大团队怎么保证 50 个人写出风格一致的代码?"它把 Angular 那一套(模块、依赖注入、装饰器)搬到后端,用框架强制分层。代价是概念多、代码量大,小项目会觉得杀鸡用牛刀。
一句话记忆:Express 最自由、Fastify 最快也最有契约、NestJS 最有规矩。
先看一张选型表
| 维度 | Express 5 | Fastify 5 | NestJS 11 |
|---|---|---|---|
| 核心抽象 | 中间件(req, res, next) | 插件 + 生命周期钩子 | 模块 + 控制器 + 提供者(DI) |
| 性能量级 | 基准 | 明显更高(通常数倍量级) | 常用 Express 适配器,另可选 Fastify 适配器 |
| TypeScript | 靠 @types/express,类型一般 |
类型友好,schema 能推类型 | 原生 TS 优先,装饰器 + DI 类型体验最好 |
| 请求校验 | 自己接 zod / express-validator | 内置 JSON Schema(Ajv),可自动生成文档 | 内置管道(Pipe)+ class-validator |
| 生态 | 最大,任何中间件都能找到 Express 版 | 官方插件齐全,Express 中间件可兼容 | 官方生态完整,第三方偏向企业能力 |
| 学习曲线 | 最平缓(半小时上手) | 中等(要理解封装与钩子) | 最陡(模块/DI/装饰器/管道/守卫/拦截器) |
| 适合 | 中小项目、内部工具、BFF、快速原型、Serverless 函数 | 高并发 API、对延迟敏感的服务、需要强契约与自动文档 | 多人协作的企业系统、需要清晰分层与长期维护 |
关于"性能"的提醒:框架吞吐量的基准测试数字,只能当量级参考,别当结论。真实系统的瓶颈几乎总是数据库、外部调用、序列化、不当的同步计算,而不是路由匹配。如果你只是"读一条记录返回 JSON",Express 和 Fastify 的差距可能还不到数据库耗时的一个零头;但如果你是高 QPS 网关、纯粹做转发和聚合,几千 vs 几万的 QPS 就是真金白银。
同一个接口,三种写法
需求:POST /users,校验 body(name 必填字符串、age 可选 0~150),成功返回 201
Fastify 深水区:schema 为什么能提速
快Fastify 快的三个真实原因
① 序列化走了"代码生成"。Express 返回对象时用 JSON.stringify,它得在运行时反射对象的每个 key 去判断类型。Fastify 拿到响应 schema 后,用 fast-json-stringify 在启动时生成一段专门针对这个结构的序列化函数(编译期确定字段和类型),运行时只需按模板填值。这是它比 Express 快的主要原因——而不是因为路由匹配快。
② 顺手做了"字段裁剪"。响应 schema 里没声明的字段不会出现在输出里。这既省带宽,又天然防了"手滑把 password 字段返回给前端"的严重事故。
③ 校验器是编译型的。Ajv 会把 JSON Schema 编译成校验函数,而不是每来一个请求就去解释一遍 schema。
Fastify 的插件封装:作用域是最容易搞混的地方
坑一:以为"换了 Fastify 就快了"。如果你的接口里有一堆 await 串行调用数据库、或者响应体没给 schema,Fastify 的速度优势基本吃不到。先优化数据库和 N+1,再谈框架。
坑二:Express 5 的通配符路由写法变了。Express 5 升级了 path-to-regexp,旧的 app.get('*', ...)、'/files/*' 这类写法不再按老语义工作,要用具名通配(如 '/*splat')。从 Express 4 升级时,先把路由通配符全部过一遍,别等上线才发现某个路由匹配不上了。
坑三:Express 5 里 async 抛错虽然会自动进错误中间件,但"中间件顺序"还是关键。错误处理中间件必须用 4 个参数 (err, req, res, next) 且写在所有路由之后,否则它会被当成普通中间件,永远不生效。
坑四:NestJS 用 @Body() dto 不等于自动校验。必须全局注册 ValidationPipe(或 @UsePipes),否则 class-validator 的装饰器只是好看的注释——这是新手最常见的"写了校验却没生效"。
怎么定:三个问题问完就有答案
- 已经用 Express 且没痛点:别为了性能数字迁移,收益远小于风险。真要提速,先上 schema 校验 + 响应裁剪 + 缓存。
- 想从 Express 迁到 Fastify:
@fastify/express可以兼容挂载 Express 中间件,能渐进迁移,不用一次性重写。 - 想要 NestJS 的结构又舍不得 Fastify 的速度:NestJS 可以用
@nestjs/platform-fastify把底层换成 Fastify,保留 DI 和装饰器的同时拿到性能。
① Express 自由、生态最大、上手最快;Fastify schema 优先,性能与契约双赢;NestJS 强分层,适合大团队长周期。
② Fastify 的快来自 schema 驱动的序列化(fast-json-stringify)+ 编译型校验(Ajv)+ 响应字段裁剪,不是路由匹配。
③ 真实瓶颈永远在数据库与外部调用;框架选型的收益主要体现在开发效率、契约清晰度和团队一致性。
④ 两个必记的"没生效"陷阱:Express 5 路由通配符语法变了、NestJS 不注册 ValidationPipe 校验就形同虚设。
1.(辨析题)Fastify 比 Express 快,主要快在哪里?请指出两个具体机制。
查看答案
答案:① 序列化:Fastify 根据响应 JSON Schema 用 fast-json-stringify 在启动时生成专用序列化代码,避免了 JSON.stringify 的运行时反射;② 校验:Ajv 把 schema 编译成校验函数,不是每次请求解释 schema。另外还有响应字段裁剪、更轻的请求/响应对象封装等因素。
2.(场景题)一个 6 人团队要做一个要维护 3 年的企业内部系统,模块多、需要权限分级、会不断有人交接。选哪个框架?为什么?
查看答案
答案:NestJS。理由不是性能,而是长期协作成本:模块/控制器/服务分层让"代码该放哪"有唯一答案;DI 让单元测试容易写;守卫(Guard)天然适合权限分级;拦截器统一处理日志与响应格式。人员轮换频繁时,框架强制的结构比个人自觉可靠得多。
3.(排错题)同事用 NestJS 写了一个创建接口,用 @Body() dto: CreateUserDto 接收数据,DTO 上写了 @IsString()。测试时传了 { age: "abc" },接口居然正常返回 201。问题在哪?
查看答案
答案:没有注册 ValidationPipe。NestJS 默认不做自动校验,class-validator 的装饰器必须在 ValidationPipe 参与时才会生效。修法是在 main.ts 里 app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true })),或用 @UsePipes() 加在控制器上。顺带提醒:不开 whitelist 时多余字段会被保留,存在被塞入额外属性的风险。
4.(选型题)你要写一个高流量的"聚合网关",把 5 个下游接口拼成一个响应返回,QPS 上万、响应体不大。Express 还是 Fastify?
查看答案
答案:Fastify。这类服务的特点是"请求处理逻辑很薄、序列化与并发量占比高",正好踩在 Fastify 的优势上:schema 序列化能显著降低单请求 CPU 开销、响应裁剪减少带宽、开销更小的请求对象让高并发下内存更友好。但注意:真正的瓶颈往往在下游接口的响应时间和并发控制,网关层要配好超时、并发上限和缓存。