Hono.js 使用笔记

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 的代码结构:

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

export default app

获取参数:

app.get('/hello/:test',
    (c) => {
        const test = c.req.param('test')
        return c.json({
            test
        })
    })

中间件

类似 KOA 的洋葱圈中间件模式:

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 参数校验

安装依赖:

npm i -S zod @hono/zod-validator

参数校验,支持param、query、json、header等,同时校验配置多个中间件即可。

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

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 类似,支持拆分文件:

// 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

在入口文件中引用:

app.route('/authors', authors).route('/books', books)

异常捕获

可以在处理函数或中间件中抛出异常:

throw new HTTPException(401, { message: "未登录" })

使用 onError 捕获:

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 推导类型。

const app = new Hono().basePath('/api')

const routes = app.route('/authors', authors).route('/books', books)

export type AppType = typeof routes

客户端项目中导入该类型,即可使用:

import { hc } from "hono/client";
import { AppType } from "./server";

const client = hc<AppType>('http://localhost:8787/')

客户端使用:

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()

客户端获取接口的入参和响应类型:

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"];
本文收录于专栏
收集一些好用的前端开源库,主要是 npm 包