Node.js 后端框架对比:Express、Koa、NestJS、Fastify,以及它们和 Java 那套的区别
面试题:Node 的后端框架你选过哪个?和 Spring 比差在哪?
一个观察:Node 框架的选型吵架,十有八九是在不同的抽象层级上吵同一件事。
Express 用户嫌 NestJS 繁琐,NestJS 用户嫌 Express 是"裸奔"——其实他们要的东西本来就不一样。
这篇先按抽象层级把四个框架排开,再对比 Node 和 Java 两套体系的真实差异。判断基于主流版本的通用认知,没有虚构压测数字。
一、四个框架,四个层级
| 框架 | 定位 | 心智模型 | 适合 |
|---|---|---|---|
| Express | HTTP 路由 + 中间件 | 一个 app,一串 use | 小服务、BFF、原型 |
| Koa | 更小的内核 + async 中间件 | 洋葱模型(见我另一篇) | 想自己搭架构的团队 |
| Fastify | 性能优先的路由框架 | schema 驱动,插件注册 | 高吞吐 API 网关、中台服务 |
| NestJS | 企业级应用框架 | 模块 / 依赖注入 / 装饰器 | 复杂业务、大团队、Java 背景的人 |
Express:事实上的"标准库"。生态最厚,教程最多,问题是一个大点的业务写到后期,路由、分层、依赖管理全靠自己约定——团队里每个人一套写法。
Koa:内核比 Express 还小,把中间件换成 async 的洋葱模型。它不是"更好的 Express",是"让你自己造更好的 Express"——Egg 就是阿里在 Koa 上加了约定之后的企业版答案。
Fastify:核心卖点是快——JSON 序列化用 schema 提前编译成机器码级函数、路由用基数树。代价是你要多写 schema。适合纯吞吐型 API;写复杂业务域时它的抽象不如 NestJS 全。
NestJS:Node 圈的 Spring——控制器 / 服务 / 模块分层,构造器注入,装饰器声明路由和依赖。它的学习成本对纯前端偏高,但对写过 Spring 的人几乎零成本迁移。默认底层就是 Fastify(可换 Express)。
选型一句话:看团队规模和业务寿命。三个人的内部工具,Express/Koa 足够;要做两年以上、多人接手的业务系统,NestJS 的约束是省钱的;纯网关和高并发 API,Fastify。这条和数据库选型一样——先问负载和团队,再报名字。
二、Node 和 Java 的区别:三个维度
1. 线程模型:事件循环 vs 线程池
这是最深的一刀。
Java(Spring 默认):每个请求一个线程(Tomcat 线程池,几百个)。阻塞式写法,代码直白;上限是线程数,CPU 密集和 IO 密集都吃得下。
Node:单线程事件循环,所有请求在一条线上轮转。IO 等待不占线程(网络、磁盘由 libuv 的线程池和系统异步接口处理),所以几万个并发连接、每个都闲着的场景(聊天、推送、API 聚合)Node 很省;但一段同步重计算会卡住所有人,CPU 密集任务必须拆 Worker 或干脆换语言。
判断口诀:IO 密集看 Node,CPU 密集看 JVM。 Node 是"一万个排队的人共用一个窗口",Java 是"两百个窗口各排各的"。前者怕柜台里聊半天(同步阻塞),后者怕窗口不够(线程数上限 + 内存开销)。
2. 类型与约束:编译期 vs 运行时
Java 的类型系统和 Spring 的 IoC 容器是绑在一起的一整套编译期约束——字段注入不存在的 Bean,启动就报错;接口改了签名,编译不过。
Node/TS 那边,TypeScript 检查的是类型,但框架层面的约束默认是薄的:NestJS 的 DI 把这事补了一大半(所以它像 Spring),Express/Koa 时代基本靠 eslint 和自觉。这也是为什么 Node 项目容易"写着写着烂掉"——约束不在语言里,在架构里。
反射这条线两边都有,但路数不同,详见我另一篇:反射和依赖注入。Java 注解是运行时真反射读的;TS 装饰器编译期就变成了 __decorate(...) 普通调用, NestJS 的"注解"全靠 reflect-metadata 在元数据里打标记——长得像 Spring,机制完全不是一套。
3. 生态与运维:两头都成熟,成熟的方向不同
- Java:事务、连接池、监控(JMX/Arthas)、GC 调优——三十年企业级运维攒下来的家伙事儿。金融、账务这类"错一分钱是事故"的领域,这套体系就是护城河。
- Node:npm 生态爆发力强,前后端同语言(类型定义、工具链、甚至 SSR 同构),迭代快。但 ORM(TypeORM/Prisma)比 MyBatis/JPA 年轻,分布式事务、成熟的多数据源管理明显薄。
我的实感:Java 的生态是"重但稳",Node 的生态是"快但散"——一个新轮子出来快,弃坑也快,选依赖时的鉴别成本要算进去。
三、一张对照表收尾
| 维度 | Node(NestJS 为代表) | Java(Spring 为代表) |
|---|---|---|
| 并发模型 | 单线程事件循环 | 线程池 |
| 擅长 | IO 密集、高并发长连接、BFF/SSR 同构 | CPU 密集、事务型业务、企业系统集成 |
| 约束来源 | 架构层(NestJS 的 DI / 装饰器) | 语言层(类型 + IoC 容器 + 启动校验) |
| 典型场景 | API 聚合层、实时推送、全栈小队 | 订单账务、报表、大型企业系统 |
| 团队画像 | 前端为主的全栈小队 | 前后端分工的传统团队 |
注意这不是"谁强"——是两种取舍。Node 用"单线程省内存 + 同语言"换掉了"多线程的吞吐上限";Spring 用"重"换掉了"轻"。
四、面试里可以这样回答
Node 框架我会按抽象层级选:Express/Koa 是路由和中间件层,适合小服务和 BFF;Fastify 是性能优先的,适合高吞吐 API;NestJS 是 Spring 那套分层和依赖注入的 Node 版,适合生命周期长的业务系统。选型时第一个看的是业务寿命和团队构成,不是框架流行度。
和 Java 的区别我认为核心在线程模型:Node 单线程事件循环,IO 等待不占线程,高并发长连接很省;代价是同步重计算卡全局,CPU 密集得拆 Worker。Java 每请求一线程,写法直白、CPU 密集吃得下,上限是线程数。所以 IO 密集我看 Node,CPU 密集和强事务看 JVM。
然后我会补一句自己的例子:什么项目选了哪边、验证下来对不对。选型题的加分项永远在"为什么不是另一个",而不是背特性清单。
Garbage in, garbage out.