Hono与Express、Fastify、Elysia对比
要点
- Express、Fastify、Hono、Elysia 分别诞生于不同年代,设计目标各异——选型之前先搞清楚它们各自在解决什么问题
- 中间件模型是框架之间最影响日常开发体验的差异:Express 线性回调、Hono 洋葱模型、Fastify 生命周期钩子、Elysia 插件封装
- 类型推导深度决定了你能在多大程度上依赖编辑器而不是文档:Express 几乎为零,Elysia 端到端覆盖
- 运行时支持直接决定部署模型:Node.js 绑定还是 Edge 可部署,是架构决策而不是偏好
- 性能差异在绝大多数业务场景中不构成选型依据——生态成熟度、团队经验、部署目标才是关键权重
- 没有「最好」的框架,只有和当前场景、团队能力、运维条件匹配度最高的框架
1. 四个框架的定位与设计哲学
每个框架都是对特定问题的回答。理解它们「为什么被造出来」,比记住 API 差异更重要。
Express:Node.js 的最小公倍数
2010 年发布,是 Node.js Web 框架的事实标准。核心设计就一句话:一切皆中间件。
Express 本身几乎不替你做决定——不内置验证、不内置序列化、不内置类型系统。它提供路由分发和中间件编排,剩下的事交给社区。这种极简主义让它活了十几年,也让它成了无数教程的起点。
代价也很明显:Node.js 生态里大量 Express 的「最佳实践」其实是补它缺失功能的补丁。body-parser、cors、helmet 这些几乎是项目标配的中间件,本质上都在弥补 Express 不提供开箱即用的事实。
Fastify:Schema 驱动的高性能替代
2017 年由 Node.js 核心团队成员创建,目标很明确:做一个比 Express 更快、更规范的 Node.js 框架。
Fastify 的核心思路是用 JSON Schema 驱动请求验证和响应序列化。你在注册路由时声明输入输出的 Schema,Fastify 用 ajv 做验证、用 fast-json-stringify 做序列化——两者都比手写 JSON.parse / JSON.stringify 快。
另一个重要设计是插件系统。Fastify 的插件有独立的上下文封装(encapsulation),子插件的装饰器和钩子不会污染父级。这套机制比 Express 的全局中间件栈更有结构感,但学习成本也更高。
Hono:Web Standards 与多运行时
2022 年发布,设计目标是用 Web 标准 API 构建一个能跑在任何 JavaScript 运行时上的框架。
Hono 的路由处理函数接收标准的 Request,返回标准的 Response。中间件基于 async/await 的洋葱模型。没有 req/res 拆分,没有 next() 回调,没有框架私有类型——你在 Hono 里写的代码,理论上可以直接搬到任何支持 Fetch API 的环境。
多运行时支持不是噱头。同一个 Hono 应用可以部署到 Cloudflare Workers、Deno Deploy、Bun、Node.js,不需要改任何代码。这在 Edge Computing 兴起的背景下是实际的架构优势。
Elysia:Bun 原生的端到端类型安全
2023 年发布,专门为 Bun 运行时设计。核心卖点是端到端类型安全:后端定义路由和验证 Schema,前端通过 Eden RPC 调用时自动获得完整的类型推导——参数类型、返回类型、错误类型全链路覆盖。
Elysia 的 API 风格受 tRPC 和 Hono 影响,但走得更远。它用装饰器风格的链式 API 做请求验证,用单例模式管理插件状态。性能在 Bun 上非常出色,但代价是完全绑定 Bun 运行时。
2. 多维度对比
一张表看完核心差异,下面逐项展开。
| 维度 | Express | Fastify | Hono | Elysia |
|---|---|---|---|---|
| 首次发布 | 2010 | 2017 | 2022 | 2023 |
| 核心定位 | 最小主义 Web 框架 | 高性能 Schema 驱动框架 | Web Standards 多运行时框架 | Bun 原生端到端类型安全框架 |
| 运行时支持 | Node.js | Node.js | Node.js / Bun / Deno / Cloudflare Workers / Fastly 等 | 仅 Bun |
| 包体积 | ~200KB | ~500KB(含 ajv) | ~14KB(核心) | ~200KB |
| TypeScript 支持 | 社区类型定义 @types/express | 内置,Schema 驱动部分推导 | 内置,完整推导 + RPC 类型 | 内置,端到端推导 + Eden RPC |
| 中间件模型 | 线性 next() 回调 | 生命周期钩子 + 插件封装 | 洋葱模型 await next() | 插件系统 + 钩子 |
| 请求验证 | 无内置,依赖中间件 | 内置 JSON Schema(ajv) | 内置 validator 中间件 | 内置装饰器验证 |
| 路由匹配 | 数组遍历 | radix tree | radix tree + 正则 | radix tree |
| 序列化 | JSON.stringify | fast-json-stringify | JSON.stringify / Streaming API | JSON.stringify / Streaming API |
| 部署模型 | 传统服务器 / 容器 | 传统服务器 / 容器 | 服务器 / Edge / 多运行时 | Bun 服务器 |
| WebSocket | 需配合 ws/socket.io | 内置 | 内置 helper | 内置 |
| ORM 集成 | 生态丰富 | 生态较好 | 生态发展中 | 生态早期 |
体积差异的实际意义
Hono 核心包只有 ~14KB,这不是为了跑分。在 Edge 环境(Cloudflare Workers、Deno Deploy)中,冷启动时间和包大小直接影响响应延迟和成本。Express 的 ~200KB 在传统服务器上没有感知,在 Edge 上会被放大。
3. 类型系统对比
类型推导是近几年 Web 框架竞争最激烈的维度。四个框架在这个方向上差异显著。
Express:类型基本靠手写
Express 本身不提供类型推导。@types/express 定义了 Request、Response、NextFunction 的基础类型,但路由参数的类型、请求体的类型、中间件注入的数据类型,都需要手动声明。
// Express — 手动声明是常态
app.get('/users/:id', (req: Request, res: Response) => {
const id = req.params.id // string,但你想让它更具体
const body = req.body // any — 完全没有类型保护
res.json({ id, body })
})这意味着 Express 项目的类型安全主要靠团队纪律和 Zod/Yup 等第三方库在运行时补。
Fastify:Schema 驱动的部分推导
Fastify 通过路由级别的 JSON Schema 做类型推导。你定义了 Schema,req.body、req.query、req.params 的类型会自动推导。
// Fastify — Schema 驱动类型
server.get<{
Querystring: { page: number }
Reply: { users: User[] }
}>('/users', {
schema: {
querystring: { type: 'object', properties: { page: { type: 'integer' } } },
response: { 200: { type: 'object', properties: { users: { type: 'array' } } } }
}
}, async (request, reply) => {
request.query.page // number — 从 Schema 推导
return { users: [] }
})推导是有的,但写法冗长。你需要同时维护 TypeScript 泛型和 JSON Schema 两套声明,存在重复定义的问题。
Hono:完整的请求-响应推导 + RPC
Hono 的类型系统覆盖三个层面:
- 路由参数推导:
:id自动推导为string - 中间件类型传播:
c.set('user', user)写入的类型,后续路由c.get('user')自动推导 - RPC 类型传递:通过
hono/client,后端路由定义的类型在前端调用时完整保留
// Hono — 后端定义
const app = new Hono()
.get('/users/:id', zValidator('param', z.object({ id: z.string() })), (c) => {
const { id } = c.req.valid('param') // { id: string } — 从 Zod Schema 推导
return c.json({ id, name: 'Alice' })
})
// Hono — 前端 RPC
const client = hc<typeof app>('http://localhost:8787')
const res = await client.users[':id'].$get({ param: { id: '1' } })
const data = await res.json() // { id: string, name: string } — 完整推导不需要手写 JSON Schema,用 Zod 等库做验证的同时自动生成类型。前后端类型一致性由编译器保证。
Elysia:端到端类型安全
Elysia 和 Hono 一样支持端到端类型推导,但它的 Eden RPC 是内置的,不需要额外安装 hono/client。
// Elysia — 后端定义
const app = new Elysia()
.get('/users/:id', ({ params }) => {
return { id: params.id, name: 'Alice' }
})
// Elysia — 前端 Eden RPC
const client = edenTreaty<typeof app>('http://localhost:3000')
const { data } = await client.users[':id'].get({ params: { id: '1' } })
// data: { id: string, name: string }Elysia 的验证用装饰器风格的链式 API,不需要 Zod(但也支持接入):
const app = new Elysia()
.post('/users', ({ body }) => body, {
body: t.Object({
name: t.String({ minLength: 1 }),
age: t.Number({ minimum: 0 })
})
})类型系统对比总结
| 维度 | Express | Fastify | Hono | Elysia |
|---|---|---|---|---|
| 路由参数类型 | 手动 | 手动或 Schema | 自动推导 | 自动推导 |
| 请求体类型 | any | Schema 推导 | Validator 推导 | 装饰器推导 |
| 响应类型 | 手动 | Schema 声明 | 自动推导 | 自动推导 |
| 中间件类型传播 | 无 | 有限 | 完整(c.set/c.get) | 通过插件上下文 |
| 前端 RPC | 无 | 无 | hono/client | Eden(内置) |
| 端到端类型安全 | 否 | 否 | 是 | 是 |
4. 中间件模型对比
中间件模型决定了你写代码时的思维模式。这是四个框架之间最影响日常开发体验的差异。
Express:线性 next() 回调
Express 的中间件是一个函数签名 (req, res, next) => void 的回调队列。每个中间件可以选择处理请求、修改 req/res,然后调用 next() 把控制权交给下一个中间件。
app.use(authMiddleware) // 1. 验证身份
app.use(rateLimiter) // 2. 限流
app.get('/api/data', handler) // 3. 处理请求这种模型简单直接,但有两个常见问题:
- 错误处理依赖
next(error):如果你忘了传 error 给 next,错误会被吞掉 - 没有统一的「后处理」机制:Express 中间件在
next()之后无法再修改响应,因为响应可能已经被下游发送了
Hono:洋葱模型
Hono 的中间件基于 await next(),形成洋葱模型。请求从外到内穿过中间件层,响应从内到外穿回来。
app.use('*', async (c, next) => {
const start = Date.now()
await next() // 请求向内传递
const ms = Date.now() - start
c.header('X-Response-Time', `${ms}ms`) // 响应向外传出时可以修改
})洋葱模型的优势是前后处理对称:你可以在 await next() 之前做前置逻辑(解析 token、记录开始时间),在之后做后置逻辑(计算耗时、修改响应头)。这种结构天然适合日志、监控、压缩等横切关注点。
错误处理也更自然——中间件里的 throw 会自动冒泡到 app.onError(),不需要手动 next(error)。
Fastify:生命周期钩子 + 插件封装
Fastify 没有传统意义上的「洋葱模型」。它把请求处理拆成了多个生命周期钩子(onRequest、preParsing、preValidation、preHandler、onSend、onResponse),你可以在每个阶段插入逻辑。
fastify.addHook('onRequest', async (request, reply) => {
// 请求到达时执行
})
fastify.addHook('preHandler', async (request, reply) => {
// 路由处理前执行
})
fastify.addHook('onSend', async (request, reply, payload) => {
// 响应发送前执行,可以修改 payload
return payload
})Fastify 的插件系统有上下文封装:子插件里注册的钩子、装饰器只在子插件内生效,不会污染全局。这解决了 Express 全局中间件栈的命名冲突问题,但学习曲线更陡。
Elysia:插件 + 生命周期钩子
Elysia 结合了插件系统和钩子,风格接近 Fastify 但更紧凑。插件可以声明自己的生命周期钩子,通过 .group() 做作用域隔离。
const authPlugin = new Elysia({ name: 'auth' })
.onBeforeHandle(({ headers, set }) => {
if (!headers.authorization) {
set.status = 401
throw new Error('Unauthorized')
}
})
app.use(authPlugin)Elysia 的 onBeforeHandle 类似 Fastify 的 preHandler,onAfterHandle 类似 onSend。概念上更接近 Fastify,API 上更简洁。
中间件模型对比总结
| 维度 | Express | Fastify | Hono | Elysia |
|---|---|---|---|---|
| 模型类型 | 线性回调 | 生命周期钩子 | 洋葱模型 | 插件 + 钩子 |
| 请求流转 | next() | 钩子按阶段执行 | await next() | 钩子按阶段执行 |
| 后处理能力 | 无(响应已发送) | onSend 钩子 | await next() 之后 | onAfterHandle 钩子 |
| 错误处理 | next(error) | 钩子内 throw | 自动冒泡到 onError | 自动冒泡到 onError |
| 作用域隔离 | 无(全局栈) | 插件封装 | 路由级 app.route() | 插件 + .group() |
| 学习成本 | 低 | 中高 | 低 | 中 |
5. 性能对比
性能是框架选型时经常被高估的维度。先看数据,再谈数据的实际意义。
基准测试数据
以下数据引用自社区基准测试(具体数值会随版本和测试环境变化,这里给出数量级参考):
| 指标 | Express | Fastify | Hono (Node.js) | Elysia (Bun) |
|---|---|---|---|---|
| 简单 JSON 响应 (req/s) | ~15,000 | ~65,000 | ~40,000 | ~160,000 |
| 带路由参数的请求 (req/s) | ~12,000 | ~55,000 | ~35,000 | ~140,000 |
| 带 5 个中间件 (req/s) | ~8,000 | ~40,000 | ~25,000 | ~100,000 |
| 内存占用 (MB) | ~60 | ~80 | ~45 | ~50 |
数据来源参考:TechEmpower Web Framework Benchmarks、Fastify 官方基准、社区独立测试。数值为近似量级,非精确对比。
数据背后的事实
几个值得注意的点:
-
Express 最慢,但「慢」是相对的。15,000 req/s 对大多数业务系统意味着单机可以支撑数千并发用户。如果你的 QPS 不超过 1,000,四个框架的性能差异不会体现在用户感知上。
-
Fastify 的高性能主要来自序列化。
fast-json-stringify根据 Schema 预编译序列化函数,比JSON.stringify快 2-3 倍。如果你的接口返回复杂对象,这个优化有实际价值。 -
Elysia 的绝对数字最高,但前提是 Bun。Bun 本身比 Node.js 快,所以 Elysia 的高性能有一部分是运行时的功劳,不完全是框架的功劳。
-
Hono 在 Edge 上的延迟优势来自地理位置。Edge 部署把计算推到离用户最近的节点,网络延迟的减少(通常 50-200ms)远大于框架层面的性能差异。
什么时候性能才是选型依据
- 单机需要支撑 10 万+ QPS 的网关或代理层 → Fastify 或 Elysia
- 需要全球低延迟的 API → Hono + Edge 部署
- 普通业务系统(QPS < 5,000)→ 性能不是选型因素
6. 生态与社区
框架不是孤岛。插件数量、文档质量、社区活跃度直接影响你的开发效率。
npm 周下载量(2026 年近似值)
| 框架 | 周下载量 | 说明 |
|---|---|---|
| Express | ~3,000 万 | Node.js 生态最大的 Web 框架,存量项目极多 |
| Fastify | ~600 万 | Node.js 高性能替代方案,增长稳定 |
| Hono | ~200 万 | 多运行时场景增长快,Edge 生态核心选择 |
| Elysia | ~20 万 | Bun 生态早期,增长与 Bun 采用率强相关 |
插件与中间件生态
| 框架 | 官方插件 | 社区插件 | 说明 |
|---|---|---|---|
| Express | 少 | 极多(数千个) | 十几年积累,几乎任何需求都有现成中间件 |
| Fastify | ~50 个 | ~500 个 | 官方维护质量高,社区活跃度中等 |
| Hono | ~30 个 | ~200 个 | 官方 helper 质量高,社区在快速增长 |
| Elysia | ~20 个 | ~50 个 | 生态早期,核心功能多内置 |
文档质量
| 框架 | 文档评分 | 说明 |
|---|---|---|
| Express | 中等 | 历史悠久,部分文档陈旧,迁移指南不够系统 |
| Fastify | 高 | 文档详尽,概念解释清晰,版本管理好 |
| Hono | 高 | 简洁实用,示例丰富,多运行时部署指南是亮点 |
| Elysia | 中高 | 内容全面但版本迭代快,部分文档和代码不同步 |
生态成熟度是一个容易被忽视但影响深远的因素。Express 的生态意味着你遇到的任何问题都能在 Stack Overflow 找到答案。Hono 和 Elysia 的生态意味着你可能需要自己写一些集成代码。
7. 场景选型建议
没有「最好」的框架。选型的核心是匹配你的场景约束。
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| 传统 Node.js 后端服务,团队熟悉 Express | Express | 生态最成熟,招人容易,资料最多 |
| 高吞吐 API 服务,Node.js 部署 | Fastify | Schema 驱动的高性能,插件封装有结构感 |
| 多部署目标(Edge + 服务器),Web Standards 优先 | Hono | 唯一真正跨运行时的框架,Edge 部署原生支持 |
| Cloudflare Workers / Deno Deploy 项目 | Hono | 对 Edge 运行时的支持和文档最完善 |
| Bun 项目 + 前后端全栈类型安全 | Elysia | 端到端类型安全体验最好,Bun 性能最优 |
| 微服务 / Serverless 函数 | Hono / Fastify | Hono 冷启动快、包小;Fastify 在 Node.js Serverless 上成熟 |
| 快速原型 / MVP | Express / Hono | Express 生态现成方案多;Hono 代码量少、上手快 |
| 已有大量 Express 中间件的遗留项目 | Express | 迁移成本高,收益不确定 |
| 需要完整前端 RPC 类型推导的全栈项目 | Elysia / Hono | Eden RPC 和 hono/client 提供端到端类型安全 |
几个常见的选型误区
「Express 太老了,应该用新的」 — 老不是缺点。Express 的中间件模型被后来所有框架借鉴。如果你的项目不需要 Edge 部署、不需要端到端类型安全,Express 仍然是可靠的选择。
「性能测试显示 X 比 Y 快 5 倍」 — 实验室性能在真实业务中很难复现。你的数据库查询、网络调用、序列化复杂度对整体延迟的影响远大于框架层面的差异。
「框架越新越好」 — 新框架意味着更少的生产验证、更薄的生态、更可能遇到未解决的 edge case。选型要看团队能承受的试错成本。
8. 本分组总结
「03-Hono 核心工作原理」到这里结束了。回顾一下这个分组的学习路径:
- 01-03:从
new Hono()的本质出发,理解 app 对象的内部结构,然后展开路由注册和匹配的底层机制——正则树和 radix tree 如何工作 - 04-06:深入请求处理的核心三角——
Context上下文对象、HonoRequest封装、Response生成流程 - 07-09:中间件体系——洋葱模型的执行顺序、
next()函数的调用链、错误冒泡与异常处理 - 10-12:边界情况处理——
notFound与onError机制、多运行时适配原理、Edge 运行的技术基础
这 12 篇从「Hono 是什么」讲到「Hono 为什么能这样工作」。你现在知道了 c.req 背后的封装逻辑、中间件洋葱模型的真实执行流程、错误是怎么一层层冒泡到 onError 的、同一个 app 为什么能跑在 Workers 和 Node.js 上。
这些知识不是孤立的。当你在 04 篇里学了 c.set() / c.get(),到 07 篇的中间件洋葱模型里就明白了它们在请求生命周期中的位置。当你在 11 篇里理解了多运行时适配,12 篇的 Edge 运行就有了技术基础。整个分组是一个层层递进的结构。
延伸阅读
- TechEmpower Web Framework Benchmarks — 多框架、多语言的标准化性能对比
- Fastify vs Express — Fastify 官方对设计差异的说明
- Hono 设计哲学 — Hono 的 Developer Experience 优先原则
- Elysia vs others — Elysia 对自身定位的描述
- Web Platform API — Hono 所基于的 Web Standards 标准定义
总结
Express、Fastify、Hono、Elysia 四个框架分别代表了 Node.js Web 开发在不同阶段的思路演进:从 Express 的最小中间件模型,到 Fastify 的 Schema 驱动高性能,到 Hono 的 Web Standards 多运行时,到 Elysia 的端到端类型安全。
选型的核心不是「谁最快」或「谁最新」,而是:
- 运行时约束:需要部署到 Edge 就选 Hono,绑定 Bun 就选 Elysia,Node.js 传统部署就在 Express 和 Fastify 之间选
- 类型安全需求:需要全栈类型推导就选 Hono 或 Elysia,否则 Express/Fastify 够用
- 生态成熟度:遗留项目和快速原型优先考虑 Express,新项目可以接受生态薄一些就选 Hono 或 Elysia
- 团队经验:团队熟悉的框架比「理论上最好」的框架更有生产力
这个分组从 Hono 的内部原理出发,最后回到框架选型的务实讨论。理解了 Hono 为什么能跨运行时、为什么类型系统这样设计、为什么中间件用洋葱模型——这些「为什么」比「怎么做」更重要,因为 API 会变,设计决策背后的取舍不会。
下一个分组进入 Hono 的实战层面——从中间件实战、数据库集成到部署上线,把这些原理知识用到真实项目里。
所属分组
03-Hono核心工作原理