12k
All articles

将 Express 应用迁移到 Hono 的实用建议

将 Express 应用迁移到 Hono?了解路由、middleware、请求体解析、错误处理和渐进式迁移的关键差异。

OpenReplay Team
OpenReplay Team
将 Express 应用迁移到 Hono 的实用建议

如果你正在维护一个 Express API,并考虑迁移到 Hono,首先需要明白的是:这并不是一次简单的查找替换式迁移。Express 构建于 Node 的 http 模块及其自有的 req/res 对象之上,而 Hono 则建立在 Fetch API 和 Web 标准之上。这一根本差异影响着一切:路由、中间件、请求处理以及响应。

在开始之前,你需要了解以下内容。

核心要点

  • Express 与 Hono 的底层基础截然不同:前者基于 Node 的 http 模块,后者基于 Fetch API 与 Web 标准。
  • Hono 将 Express 中独立的 reqres 对象统一为一个上下文对象 c,且处理函数必须返回一个 Response
  • 中间件签名也不同——Express 使用 (req, res, next),而 Hono 使用 (c, next),并需要 await next()
  • 请求体解析直接在 Hono 处理函数内部完成,而非通过全局中间件。
  • 从无状态的 JSON 路由开始的增量式迁移,远比一次性重写要安全得多。

先理解架构层面的差异

Express 封装了 Node 的 IncomingMessageServerResponse,因此每一个 reqres 对象都是 Node 专有的。相比之下,Hono 使用标准的 RequestResponse 对象——与你在浏览器或 Cloudflare Worker 中使用的对象完全一致。

当在 Node.js 上运行 Hono 时,需要使用 @hono/node-server 适配器来弥合差异。但处理函数本身保持运行时无关性。

// Express
app.get('/users/:id', async (req, res) => {
  const user = await db.findById(req.params.id)
  res.json(user)
})

// Hono
app.get('/users/:id', async (c) => {
  const user = await db.findById(c.req.param('id'))
  return c.json(user)
})

形态相似,但 c(上下文对象)取代了 reqres。你返回的是一个 Response,而不是调用 res 上的方法。

将 Node.js API 迁移到 Hono:从无状态路由入手

将 Express 应用迁移到 Hono 时,最稳妥的起点是无状态的、仅返回 JSON 的路由——不涉及文件系统访问、不依赖 Node 专有的流,也不使用会话中间件。这类路由可以干净利落地完成转换。

而依赖 Node 专有 API(如 req.socketres.localsres.sendFile)的路由则需要更多考量。在着手全面迁移之前,先将这些路由隔离出来,搞清楚自己面对的是什么。

Hono 中间件迁移:不要想当然地认为兼容

Express 中间件遵循 (req, res, next) 的模式。Hono 中间件则使用 (c, next),通常调用 await next()。两者并不可以互换。

// Express 中间件
app.use((req, res, next) => {
  req.startTime = Date.now()
  next()
})

// Hono 中的等效写法
app.use(async (c, next) => {
  c.set('startTime', Date.now())
  await next()
})

对于常用的包,Hono 提供了官方对应的实现:

ExpressHono 对应实现
cors()hono/cors
helmet()hono/secure-headers
express.json()通过 c.req.json() 内置支持
morganhono/logger 或自定义中间件

建议主动重写中间件,而不是试图去包装 Express 中间件——这种抽象层很少能完美适配。

请求体与响应处理

在 Express 5 中,请求体解析仍然基于中间件。而在 Hono 中,你直接在处理函数中解析请求体:

// Hono 请求体解析
app.post('/items', async (c) => {
  const body = await c.req.json()
  return c.json({ received: body }, 201)
})

响应始终是被返回的,而不是被修改的。这里没有 res.status(201).json(...)——你需要将状态码作为第二个参数传给 c.json()

错误处理

Express 使用四参数的错误处理函数 (err, req, res, next)。Hono 则使用 app.onError

app.onError((err, c) => {
  console.error(err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

渐进式迁移,而非一次性全部重写

完全重写极少能顺利推进。更好的方式是:

  1. 一次只迁移一组隔离的路由。
  2. 在过渡期内让 Hono 与 Express 并行运行。
  3. 显式重写中间件——不要包装它。
  4. 将 Node 专有的依赖(如文件处理、遗留的认证逻辑)留到最后处理。

结语

一旦你接受了 Express 与 Hono 建立在不同基础之上的事实,将 Express 应用迁移到 Hono 其实并不复杂。其路由语法足够熟悉,大多数路由几分钟内就能完成转换。真正的工作量在于中间件,以及任何直接接触 Node 专有 API 的部分。审慎地处理这些环节,迁移就会变得可控。

常见问题

迁移期间,能否让 Hono 和 Express 并行运行?

可以。一种常见的做法是在两者前面架设一个反向代理或一个小型 Node 入口,将特定路径路由到 Hono 应用,其余仍交给 Express。这样你就能一次迁移一组路由,并在出现问题时快速回滚,而不必押注在一次风险较高的整体切换上。

Hono 是只能在边缘运行时上运行,还是也支持 Node.js?

Hono 可以运行在 Node.js、Bun、Deno、Cloudflare Workers、AWS Lambda 等多种运行时上。在 Node 上,你需要使用 @hono/node-server 适配器来衔接 Fetch API 模型与 Node 的 http 模块。同一份处理函数代码可以在不同运行时之间保持可移植性,这是 Hono 相对 Express 的主要优势之一。

有没有办法在 Hono 中复用现有的 Express 中间件?

通常来说,没有。Express 中间件依赖 Node 的 req、res 对象以及 next 回调,而 Hono 中间件运行在 Fetch 风格的上下文之上。社区中确实有一些适配器,但它们往往比较脆弱。推荐的做法是基于 Hono 的 API 重写中间件,因为大多数常见需求已经有官方或内置的等效实现。

Express 与 Hono 的性能对比如何?

得益于轻量级路由器和基于 Fetch 的设计,Hono 通常比 Express 更快,尤其在路由处理和 JSON 响应方面。实际收益取决于你的具体负载——数据库查询和外部调用通常才是主要瓶颈。除非你的基准测试显示路由开销确实成为瓶颈,否则建议将性能提升视为迁移的副产品,而不是迁移的主要动因。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.