Hono.js 是目前比较流行的后端框架,支持所有 JS 运行时,使用简便,路由和中间件语法类似 express/koa ,可很方便地结合 zod 进行参数校验,支持类似 tRPC 的前后端 RPC 同构能力。
初始化
https://hono.dev/docs/getting-started/basic
默认支持预设:
aws-lambda
bun
cloudflare-pages
cloudflare-workers
deno
fastly
nextjs
nodejs
vercel
代码结构
类似于 Express 的代码结构:
1 2 3 4 5 6 7 8 9
| import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => { return c.text('Hello Hono!') })
export default app
|
获取参数:
1 2 3 4 5 6 7
| app.get('/hello/:test', (c) => { const test = c.req.param('test') return c.json({ test }) })
|
中间件
类似 KOA 的洋葱圈中间件模式:
1 2 3 4 5 6
| app.use(async (c, next) => { const start = Date.now() await next() const end = Date.now() c.res.headers.set('X-Response-Time', `${end - start}`) })
|
结合 zod 参数校验
安装依赖:
1
| npm i -S zod @hono/zod-validator
|
参数校验,支持param、query、json、header等,同时校验配置多个中间件即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| app.post('/create/:postId', zValidator("json", z.object({ name: z.string(), userId: z.number(), })), zValidator("param", z.object({ postId: z.number(), })), (c) => { const { postId } = c.req.valid("param") const { name, userId } = c.req.valid("json") return c.json({ name, userId, postId }) })
|
原始内容转换后校验,如将 url 参数的 id 转换为 number
1 2 3 4 5 6 7
| app.get('/test/:id', zValidator('param', z.object({ id: z.coerce.number() })), async c => { const { id } = c.req.valid('param'); console.log(typeof id, 'id'); return c.json({ id }) })
|
路由拆分
hono.js 不推荐使用 controller 的模式去拆分路由。
与 express 类似,支持拆分文件:
1 2 3 4 5 6 7 8 9
| // books.ts import { Hono } from 'hono'
const app = new Hono() .get('/', (c) => c.json('list books')) .post('/', (c) => c.json('create a book', 201)) .get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
|
在入口文件中引用:
1
| app.route('/authors', authors).route('/books', books)
|
异常捕获
可以在处理函数或中间件中抛出异常:
1
| throw new HTTPException(401, { message: "未登录" })
|
使用 onError 捕获:
1 2 3 4 5 6 7
| app.onError((err, c) => { if (err instanceof HTTPException) { return c.json({ error: err.message }, err.status) } console.error(err) return c.json({ error: '服务器未知错误' }, 500) })
|
RPC
hono.js 的 rpc 非常实用,一方面是类似于 express 这样标准的 rest api ,又可获取类似于 tPRC 的前后端同构能力。
在后端项目中导出类型,可通过 pnpm monorepo 在前后端项目间共享类型。如果使用 Next.js 这类前后端同构的框架,可直接获得对应的类型。
需注意,定义路由时要用链式结构连接所有路由,方便 ts 推导类型。
1 2 3 4 5
| const app = new Hono().basePath('/api')
const routes = app.route('/authors', authors).route('/books', books)
export type AppType = typeof routes
|
客户端项目中导入该类型,即可使用:
1 2 3 4
| import { hc } from "hono/client"; import { AppType } from "./server";
const client = hc<AppType>('http://localhost:8787/')
|
客户端使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const client = hc<AppType>('http://localhost:8787/')
const res = await client.api.books.create[':postId'].$post({ json: { name: '11', userId: 1 }, param: { postId: '111' } })
const data = await res.json()
|
客户端获取接口的入参和响应类型:
1 2 3 4 5 6 7 8 9
| import { InferResponseType, InferRequestType } from "hono";
// 获取响应类型 type LoginResponseType = InferResponseType<typeof client.api.users.login.$post>;
// 获取请求参数类型 type LoginRequestType = InferRequestType<typeof client.api.users.login.$post>; // 只读取请求参数类型中的 json 部分 type LoginRequestBodyType = InferRequestType<typeof client.api.users.login.$post>["json"];
|