Hono + tRPC:类型安全的端到端 API
使用 @hono/zod-openapi 生成 OpenAPI 文档
在现代全栈开发中,类型安全和API 可视化是提升开发效率与协作质量的关键。Hono 与 tRPC 的理念高度契合 —— 都追求 端到端的类型安全(end-to-end typesafety),无需手动定义 DTO 或维护 Swagger 注解。
本文将展示如何结合 Hono 与 tRPC-like 类型模式,并使用 @hono/zod-openapi 自动生成 OpenAPI (Swagger) 文档,实现既类型安全又可文档化的 API 服务。
一、为什么 Hono + tRPC 是理想组合?
| 特性 | 说明 |
|---|---|
| 类型安全 | 前端调用 API 时自动获得类型提示 |
| 无 GraphQL / REST 模板 | 使用函数式调用,类似 tRPC |
| 轻量高效 | Hono ~2KB,适合边缘部署 |
| 自动生成 OpenAPI | 使用 Zod 定义输入输出,一键生成文档 |
| 开发体验佳 | TypeScript 全链路支持 |
注意:tRPC 原生不直接支持 Hono,但我们可以模仿 tRPC 的类型模式,并结合 Hono 的路由与中间件,实现类似体验。
二、核心架构:类型即契约
+----------------+ +------------------+ +-----------------+
| Frontend | ----> | Hono API Route | <---- | Zod Schema |
| (TypeScript) | | (with @hono/trpc) | | (Input/Output) |
+----------------+ +------------------+ +-----------------+
| |
+-------------------> @hono/zod-openapi <-----------+
生成 OpenAPI JSON我们使用:
Zod定义请求/响应结构;@hono/zod-openapi中间件生成 OpenAPI 文档;- 自定义
trpc()工具实现 tRPC 风格的路由定义。
三、安装依赖
bash
npm install hono zod
npm install -D @hono/zod-openapi swagger-ui-react或使用 Bun:
bash
bun add hono zod
bun add -D @hono/zod-openapi swagger-ui-react四、定义 tRPC 风格的路由工厂
ts
// lib/trpc.ts
import { Hono } from 'hono'
import { z } from 'zod'
type ProcedureType = 'query' | 'mutation'
type RouterDef = {
method: string
path: string
request: { params?: any; query?: any; body?: any }
responses: { [code: string]: any }
}
type BuildRoute = (router: Hono) => void
export const createRouter = () => {
const routers = new Hono()
const createRoute = (type: ProcedureType) => {
return {
input: <T extends z.ZodTypeAny>(schema: T) => {
return {
output: <U extends z.ZodTypeAny>(outputSchema: U) => {
return {
handler: (handler: (input: {
input: z.infer<T>,
c: any
}) => Promise<z.infer<U>>) => {
return {
meta: { type, input: schema, output: outputSchema },
handler
}
}
}
}
}
}
}
}
const query = createRoute('query')
const mutation = createRoute('mutation')
return { routers, query, mutation }
}五、定义类型安全的 API 路由
ts
// routes/user.route.ts
import { createRouter, query, mutation } from '../lib/trpc'
import { z } from 'zod'
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
})
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
})
export const userRouter = createRouter()
// GET /user/:id
userRouter.routers.get('/user/:id',
userRouter.query
.input(z.object({ id: z.coerce.number() }))
.output(userSchema)
.handler(async ({ input, c }) => {
// 模拟数据库查询
return { id: input.id, name: 'John Doe', email: 'john@example.com' }
})
)
// POST /user
userRouter.routers.post('/user',
userRouter.mutation
.input(createUserSchema)
.output(userSchema)
.handler(async ({ input, c }) => {
// 模拟创建用户
return { id: 123, ...input }
})
)
export type UserRouter = typeof userRouter六、使用 @hono/zod-openapi 生成 OpenAPI 文档
ts
// app.ts
import { Hono } from 'hono'
import { swaggerUI } from '@hono/swagger-ui'
import { OpenAPIHono } from '@hono/zod-openapi'
import { userRouter } from './routes/user.route'
const app = new OpenAPIHono()
// 注册路由
app.route('/api', userRouter.routers)
// 生成 OpenAPI 文档
app.doc('/doc', {
info: {
title: 'My API',
version: '1.0.0',
description: 'A tRPC-like API with OpenAPI docs'
},
openapi: '3.1.0',
servers: [
{ url: 'http://localhost:3000' }
],
tags: [
{ name: 'User', description: 'User management endpoints' }
]
})
// 提供 Swagger UI
app.get('/swagger', swaggerUI({ url: '/doc' }))
export default app启动后访问:
http://localhost:3000/doc→ OpenAPI JSONhttp://localhost:3000/swagger→ 可视化 Swagger UI
七、前端类型安全调用(类似 tRPC)
ts
// client/api.ts
import type { UserRouter } from '../server/routes/user.route'
import { z } from 'zod'
type InferRoute<T> = T extends { handler: () => infer U } ? U : never
type InferInput<T> = T extends { meta: { input: infer U } } ? z.infer<U> : never
export class ApiClient {
private baseUrl: string
constructor(baseUrl: string = '/api') {
this.baseUrl = baseUrl
}
// GET /user/:id
async getUser(id: number) {
const res = await fetch(`${this.baseUrl}/user/${id}`)
return await res.json() as z.infer<typeof userSchema>
}
// POST /user
async createUser(data: InferInput<typeof userRouter>) {
const res = await fetch(`${this.baseUrl}/user`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
return await res.json()
}
}
// 使用时自动获得类型提示
const api = new ApiClient()
const user = await api.getUser(1) // ✅ 类型自动推断
const newUser = await api.createUser({ name: 'Alice', email: 'alice@example.com' }) // ✅ 输入校验八、自动注册 OpenAPI 元数据(进阶)
你可以封装一个装饰器或工厂函数,自动将路由信息注册到 OpenAPI:
ts
// lib/openapi.ts
import { z } from 'zod'
import { userSchema, createUserSchema } from '../routes/user.route'
export const userDocs = {
getUser: {
summary: 'Get user by ID',
description: 'Returns a single user object',
tags: ['User'],
request: {
params: z.object({ id: z.string() }),
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: userSchema
}
}
}
}
}
}然后在路由中使用 @hono/zod-openapi 的 openapi() 中间件:
ts
import { openapi } from '@hono/zod-openapi'
app.get('/user/:id',
openapi(userDocs.getUser),
async (c) => {
const id = Number(c.req.param('id'))
return c.json({ id, name: 'John', email: 'john@example.com' })
}
)九、优势对比
| 方案 | 类型安全 | OpenAPI | 性能 | 学习成本 |
|---|---|---|---|---|
| Express + Swagger | ❌ 手动维护 | ✅ | ❌ | 高 |
| tRPC + Node.js | ✅ | ❌(无 OpenAPI) | ✅ | 中 |
| Hono + Zod + OpenAPI | ✅ | ✅ | ✅✅✅ | 低 |
Hono + Zod + @hono/zod-openapi = 类型安全 + 文档化 + 高性能
十、总结
通过结合 Hono 与 tRPC 风格的类型模式,并使用 @hono/zod-openapi,你可以:
| 目标 | 实现方式 |
|---|---|
| 端到端类型安全 | 使用 Zod 定义输入输出,前端自动推断 |
| 自动生成 OpenAPI | @hono/zod-openapi 从 Zod Schema 生成文档 |
| 高性能路由 | Hono 轻量、快速,适合边缘部署 |
| 类似 tRPC 的体验 | 函数式调用,无 REST 模板代码 |
| 可视化 API 文档 | 集成 Swagger UI,便于调试和协作 |
这不是 tRPC,但它是 tRPC 的精神继承者 —— 更轻、更快、更开放。
在 Hono 的世界里,你可以拥有 tRPC 的类型安全,同时获得 OpenAPI 的标准化和可视化能力。
立即尝试构建你的 类型安全、文档齐全、性能卓越 的下一代 API 服务!