在边缘计算与多运行时并存的今天,Hono 凭借「一次编写、处处运行」的设计迅速走红。它是一套极轻量、超快的 Web 框架,用统一的 API 同时跑在 Cloudflare Workers、Deno、Bun 和 Node.js 上,让你不必为每个平台重写路由与中间件。本文带你从零搭建一个生产可用的 Hono 服务,覆盖路由、中间件、JWT 鉴权、请求校验、测试与多平台部署。
一、为什么需要 Hono:一次编写,处处运行
过去在 Cloudflare Workers 上要学自己的 API,在 Deno 上又是另一套,迁移成本极高。Hono 抽象出统一的 c.env、c.req、c.res 接口,底层自动适配各运行时的 Request/Response。这意味着同一份代码可以无缝部署到边缘节点(低延迟)或传统 Node 服务器(易运维)。
核心优势可以归纳为三点:
- 体积小:核心 gzip 后不到 14KB;
- 快:基于原生 fetch,无框架包袱;
- 跨运行时:同一份 handler 跑遍 Workers / Deno / Bun / Node。
更重要的是,Hono 建立在 Web 标准之上(Fetch API、Request、Response、Streams),你在这里学到的写法可以原样迁移到任何遵循标准的运行时,而不是被某个框架的私有 API 绑架。对团队而言,这意味着新成员的上手成本更低、代码复用率更高,技术债也更少。当边缘与中心化部署共用同一套心智模型时,架构演进的摩擦会显著下降。
二、快速起步:从零搭建 Hono 服务
安装与最小示例
以 Bun 为例,初始化项目并安装 Hono:
bun init -y
bun add hono
# 最小服务 src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export default app
用 bun run --hot src/index.ts 即可在本地 3000 端口启动。注意我们直接 export default app,这是为了让适配器(adapter)接管监听,而非自己调用 serve。
路由与参数
Hono 的路由支持路径参数、正则与查询解析,语法接近现代框架:
app.get('/users/:id', (c) => {
const id = c.req.param('id')
const page = c.req.query('page') ?? '1'
return c.json({ id, page })
})
// 通配与正则
app.get('/assets/*', (c) => c.text('wildcard'))
app.get('/post/:date{[0-9]+}', (c) => c.text('only digits'))
三、中间件:日志、计时与 CORS
中间件以洋葱模型执行,非常适合做横切逻辑。Hono 社区已内置一批常用中间件:
import { logger } from 'hono/logger'
import { timing } from 'hono/timing'
import { cors } from 'hono/cors'
app.use('*', logger())
app.use('*', timing())
app.use('/api/*', cors({ origin: 'https://fsdata.site' }))
// 自定义计时中间件
app.get('/slow', async (c, next) => {
c.set('server-timing', 'db') // 标记阶段
await next()
})
四、JWT 鉴权实战
用 @hono/jwt 可以几行实现登录签发与接口校验。先在环境变量放密钥:
import { sign, verify } from 'hono/jwt'
const secret = 'my-super-secret'
// 登录签发
app.post('/login', async (c) => {
const { user } = await c.req.json()
const token = await sign({ user, exp: Date.now() / 1000 + 3600 }, secret)
return c.json({ token })
})
// 保护路由
app.use('/api/*', async (c, next) => {
const auth = c.req.header('Authorization')?.replace('Bearer ', '')
if (!auth) return c.text('Unauthorized', 401)
try { c.set('jwtPayload', await verify(auth, secret)) }
catch { return c.text('Invalid token', 401) }
await next()
})
更简洁的做法是直接使用 hono/jwt 提供的 jwt() 中间件,传入 secret 即可自动校验。密钥务必从环境变量读取,切勿硬编码进仓库。
五、请求校验:用 zod 守住入口
外部输入永远不可信。借助 @hono/zod-validator,可在路由层用 zod schema 拦截非法请求,校验失败自动返回 400:
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const schema = z.object({
title: z.string().min(1).max(100),
price: z.number().positive(),
})
app.post('/products', zValidator('json', schema), (c) => {
const data = c.req.valid('json') // 已通过校验
return c.json({ ok: true, data }, 201)
})
把校验前置到路由入口,业务代码就能假设数据是干净的,复杂度显著下降。
六、测试:不依赖运行时也能跑
Hono 的 app.request() 方法可以在不启动服务器的情况下直接发请求,配合 @hono/testing 做单元测试极为顺手:
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest'
import app from './index'
describe('products', () => {
it('拒绝非法价格', async () => {
const res = await app.request('/products', {
method: 'POST',
body: JSON.stringify({ title: 'x', price: -1 }),
headers: { 'Content-Type': 'application/json' },
})
expect(res.status).toBe(400)
})
})
接口测试之外,端到端场景可复用 Playwright 的 E2E 能力(参考 《Playwright 实战:端到端测试》),形成「单元 + E2E」的双层防护。
七、部署:Cloudflare Workers / Deno / Bun / Node
Cloudflare Workers
Workers 下用 hono/adapter 的 export default { fetch: app.fetch } 即可,配合 Wrangler:
// wrangler.toml
name = "hono-demo"
main = "src/index.ts"
compatibility_date = "2024-09-23"
// 入口
export default { fetch: app.fetch }
CI 部署可复用 GitHub Actions 流水线(见 《GitHub Actions 实战》),提交即自动发布到边缘节点。
Bun / Node 与自建服务器
在 Bun 或 Node 上,用官方适配器监听端口;若部署到自有服务器,建议放在 Nginx 反代之后(见 《Nginx 反向代理实战》),统一处理 TLS 与静态资源:
import { serve } from '@hono/node-server'
serve({ fetch: app.fetch, port: 3000 }, (info) => {
console.log(`listening on :${info.port}`)
})
八、进阶:RPC 与端到端类型安全
Hono 提供 hc() 客户端,可复用服务端的类型定义,让前端调用自动获得类型提示与校验,避免前后端接口漂移:
import { hc } from 'hono/client'
import type { App } from './index'
const client = hc<App>('http://localhost:3000')
const res = await client.users[':id'].$get({ param: { id: '1' } })
const data = await res.json()
九、Hono 与 Express / Elysia 怎么选
三者定位不同,按场景取舍:
| 维度 | Hono | Express | Elysia |
|---|---|---|---|
| 运行时 | Workers/Deno/Bun/Node | Node 为主 | Bun 为主 |
| 体积 | 极小 | 中等 | 小 |
| 类型安全 | 强(含 RPC) | 弱 | 强 |
| 适用场景 | 边缘 / 多运行时 | 传统 Node 服务 | Bun 全栈 |
十、生产落地检查清单
- 密钥与 JWT secret 全部走环境变量,禁止硬编码;
- 所有写接口用 zod 校验,不信任任何外部输入;
- 生产开启 logger 与 timing,定位慢请求;
- 边缘部署配 CI 自动发布,自建服务器配 Nginx + TLS;
- 核心接口补单元测试,关键链路补 E2E;
- 用 Docker 固化运行环境(见 《Docker 实战》)便于回滚。
十一、性能表现:为什么 Hono 这么快
Hono 的快并非营销话术。它几乎不引入抽象开销:没有 Express 那样的层层中间件回调包装,而是直接封装原生 fetch,路由匹配使用 Radix Tree,复杂度接近 O(log n)。在第三方基准测试里,Hono 在 Cloudflare Workers 与 Bun 上通常位居第一梯队,处理纯 JSON 响应的吞吐远高于同为 Node 老牌框架的 Express。
| 框架 | 运行时 | 相对吞吐 |
|---|---|---|
| Hono | Bun | 极高 |
| Elysia | Bun | 极高 |
| Fastify | Node | 高 |
| Express | Node | 中等 |
不过要泼一盆冷水:真实业务的瓶颈往往在数据库与网络 IO,框架层面的差异只有在压测里才明显。所以选 Hono 的真正理由,依然是「跨运行时一致性」与顺滑的开发体验,而不是单点的基准数字。
十二、常见坑与排错清单
- 本地能跑、部署报错:多半是用了 Node 专属 API(如直接操作
Buffer),边缘运行时并没有,应改用标准TextEncoder与Uint8Array。 - 中间件顺序错乱:洋葱模型下
app.use的注册顺序决定执行顺序,认证中间件必须放在受保护路由之前注册,否则等于没加锁。 - CORS 预检失败:确保
cors()覆盖了OPTIONS方法,且允许的 origin 与前端真实域名完全一致,多一个斜杠都会让浏览器拦截。 - 环境变量读不到:Workers 用
c.env,Node 用process.env,Bun 两者皆可,写混了就会拿到 undefined。 - 类型推导断掉:RPC 客户端
hc()依赖导出的app类型,记得用export type AppType = typeof app暴露,前端才能拿到推断。
结语
Hono 的价值不在于「又一个框架」,而在于它把边缘与多运行时的碎片化 API 收敛成一套清爽、类型安全、可测试的接口。无论是做低延迟的边缘 API,还是想用同一份代码兼顾多个平台,Hono 都值得放进你的工具箱。从最小示例开始,逐步加上鉴权、校验与测试,一个生产级服务就成型了。




