RPC与OpenAPI对比
要点
- OpenAPI 是一份语言无关的接口规约,用 YAML/JSON 描述接口,再通过代码生成工具产出各语言的客户端和服务端骨架
- Hono RPC 走的是另一条路:代码即规约,从 TypeScript 路由定义中直接提取类型,不需要额外的中间表示
- 两种方案解决的是同一个问题——前后端接口契约的同步——但切入点不同,一个从文档出发,一个从代码出发
- 选型的判断依据不是「哪个更先进」,而是团队技术栈、客户端语言分布、接口变更频率和协作模式
- 两者并不互斥。
hono-openapi可以从 Hono 路由定义生成 OpenAPI 文档,兼顾内部开发体验和外部接口契约 - 从 OpenAPI 迁移到 RPC 或反向迁移,主要成本不在代码,而在团队工作流的调整
- 性能层面的差异集中在构建期,运行时两者的开销都可以忽略不计
1. OpenAPI 是什么
OpenAPI 是一套描述 HTTP 接口的规约。早期叫 Swagger,2016 年 Linux 基金会接管后改名为 OpenAPI,当前主流版本是 3.0 和 3.1。
它的工作方式是「规约先行」:
- 开发者用 YAML 或 JSON 写一份接口描述文件,定义路径、方法、请求参数、响应结构、认证方式
- 用代码生成工具(openapi-typescript、openapi-generator、Swagger Codegen)从这份文件生成各语言的客户端或服务端代码
- 运行时校验、文档站点(Swagger UI)、Mock 服务器等工具也围绕这份文件运转
一份典型的 OpenAPI 文件长这样:
# openapi.yaml
openapi: 3.1.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
operationId: getUsers
parameters:
- name: page
in: query
schema:
type: integer
responses:
'200':
description: 用户列表
content:
application/json:
schema:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
post:
operationId: createUser
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserInput'
responses:
'201':
description: 创建成功
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
CreateUserInput:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string前端用 openapi-typescript 生成类型:
# 终端命令
npx openapi-typescript openapi.yaml -o api-schema.ts// api-schema.ts(自动生成,不要手动编辑)
import type { components } from './api-schema'
// 生成的类型可以直接使用
type User = components['schemas']['User']
type CreateUserInput = components['schemas']['CreateUserInput']这套流程的核心特征是:接口契约是一份独立的文件,和实现代码分离。文档就是真相,代码要跟着文档走。
2. Hono RPC 的做法
Hono RPC 走了一条完全不同的路。它不需要独立的规约文件,直接从 TypeScript 路由代码中提取类型信息。
03 篇已经详细讲过这套机制,这里只回顾关键步骤:
// server.ts
// 后端定义路由,链式写法保留类型信息
const route = app
.get('/users', zValidator('query', querySchema), async (c) => {
const users = await db.users.findMany()
return c.json({ users })
})
.post('/users', zValidator('json', createUserSchema), async (c) => {
const data = c.req.valid('json')
const user = await db.users.create(data)
return c.json({ user }, 201)
})
// 导出路由类型
export type AppType = typeof route// client.ts
// 前端直接引入类型,hc 自动生成带类型提示的客户端
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787')
// 参数、响应体全部有类型推导
const res = await client.users.$get({ query: { page: '1' } })
const data = await res.json()
// data.users 的类型自动推导核心区别在于:没有中间文件,代码本身就是契约。后端改了 createUserSchema 里的字段,前端 TypeScript 编译直接报错,不需要重新生成任何东西。
3. 对比维度拆解
两种方式解决的是同一个问题,但实现路径不同。下面按几个维度逐项对照。
3.1 开发体验
OpenAPI 的开发流程是多步的:
- 写或更新
openapi.yaml - 运行代码生成命令
- 把生成的类型文件提交到仓库
- 前端引入生成的类型
后端改了接口字段,如果忘了同步更新 YAML,前端不会收到任何提示——类型文件和后端实现之间没有自动绑定关系。
Hono RPC 的流程更短:
- 后端改路由代码或 Zod schema
- 前端
tsc编译自动报错
省掉了 YAML 维护、代码生成命令、提交生成文件这些中间步骤。代价是前后端必须在同一个 TypeScript 项目里,或者至少能通过包引用共享类型。
3.2 类型安全
OpenAPI 的类型安全取决于代码生成工具的实现质量。openapi-typescript 生成的是纯类型定义,不做运行时校验。你仍然需要自己写请求发送逻辑,或者用生成的客户端类库。
Zod schema 转 OpenAPI schema 的映射也不是 100% 无损的。比如 Zod 的 .transform()、.refine() 这类运行时逻辑,OpenAPI 描述不出来,只能标注输入输出的基本类型。
Hono RPC 的类型链条是:Zod schema → Hono 路由类型 → hc 客户端推导。整条链路都在 TypeScript 类型系统内完成,中间没有跨语言转换。Zod 的 z.object({ name: z.string() }) 既是运行时校验规则,也是类型推导来源,一份定义两用。
3.3 维护成本
OpenAPI 的维护成本集中在「YAML 和代码的双向同步」。几种常见做法:
- 手写 YAML,后端代码照着 YAML 写——容易忘记同步
- 用
tsoa、routing-controllers-openapi这类框架从代码注解生成 YAML——引入了框架约束 - 用
hono-openapi从 Hono 路由生成 YAML——后面单独讲
Hono RPC 的维护成本集中在「链式路由写法的约束」和「monorepo 的工程组织」。如果团队习惯拆分路由文件、用 app.route() 挂载,类型导出需要多一层注意(03 篇第 8 节讲过这个问题)。
3.4 多语言客户端
这是 OpenAPI 的优势领域。OpenAPI 规约是语言无关的,openapi-generator 支持生成 Java、Go、Python、Swift、Kotlin、Ruby 等 50 多种语言的客户端代码。
Hono RPC 的 hc 客户端只能用于 TypeScript。如果你的客户端是 Python 脚本、iOS App、Android App,或者对接方是不使用 TypeScript 的第三方,RPC 的类型推导就用不上了。
这种情况下 OpenAPI 是更合适的选择——生成一份 YAML,所有语言的客户端都能自动生成。
3.5 文档与可视化
OpenAPI 配套生态非常成熟。Swagger UI 可以从 YAML 文件自动生成可交互的 API 文档站点,开发者可以直接在浏览器里试接口。Redoc 提供另一种排版风格。Postman、Insomnia 都支持直接导入 OpenAPI 文件。
Hono RPC 本身不提供文档生成能力。你写了路由代码,类型安全有了,但「人类可读的 API 文档」需要额外补充。这也是 hono-openapi 存在的意义——后面会展开讲。
4. 什么时候选 OpenAPI
满足以下任一条件,OpenAPI 大概率是更合适的选择:
- 客户端语言多样。团队里有 Python 后端、iOS/Android 客户端、第三方对接方,需要为多种语言生成客户端代码
- 对外公开 API。接口的使用者是外部开发者,他们需要稳定的文档和 SDK,OpenAPI 是行业通用标准
- 接口契约需要先于实现确定。产品和前端、后端、客户端多方需要就接口格式提前达成一致,一份 YAML 文件比 TypeScript 类型更适合作为跨角色沟通的媒介
- 已有成熟的 API 管理平台。公司级别用 Apigee、Kong、AWS API Gateway 这类网关,它们原生支持 OpenAPI 导入
在这些场景下,OpenAPI 的「规约先行」模式有结构性优势——接口契约独立于任何语言的实现,所有参与方对着同一份文件工作。
5. 什么时候选 Hono RPC
满足以下条件,Hono RPC 的开发效率更高:
- 全 TypeScript 技术栈。前后端都用 TypeScript,代码在同一个 monorepo 里
- 接口变更频繁。早期产品阶段,接口几乎每天都在调整,维护 YAML 的成本高于直接改代码
- 团队规模小。一到三个开发者,不需要跨角色文档协作,代码即文档可以接受
- 对 Zod 已有依赖。后端已经在用 Zod 做请求校验,schema 定义就在那里,RPC 可以直接复用
简单判断:如果你的项目里前后端都是 TypeScript,并且代码在同一个仓库,Hono RPC 几乎不需要额外成本就能用起来。
6. 两者可以共存
OpenAPI 和 Hono RPC 不互斥。hono-openapi 这个库可以从 Hono 路由定义中生成 OpenAPI 文档,兼顾内部的开发体验和外部的文档需求。
做法是把 Zod schema 同时用于 RPC 类型推导和 OpenAPI 文档生成:
// server.ts
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
const getUsersRoute = createRoute({
method: 'get',
path: '/users',
request: {
query: z.object({
page: z.string().optional().openapi({ example: '1' }),
limit: z.string().optional().openapi({ example: '10' }),
}),
},
responses: {
200: {
content: {
'application/json': {
schema: z.object({
users: z.array(z.object({
id: z.number(),
name: z.string(),
email: z.string(),
})),
}),
},
},
description: '用户列表',
},
},
})
const app = new OpenAPIHono()
// 注册路由,handler 里有完整的类型推导
app.openapi(getUsersRoute, (c) => {
const { page, limit } = c.req.valid('query')
// page 和 limit 都有类型
return c.json({
users: [{ id: 1, name: 'Alice', email: '[email protected]' }],
})
})
// 生成 OpenAPI 文档
app.doc('/doc', {
openapi: '3.1.0',
info: { title: 'User API', version: '1.0.0' },
})访问 /doc 端点会返回一份标准的 OpenAPI JSON 文档,可以直接喂给 Swagger UI:
// docs.ts
import { Scalar } from '@scalar/hono-api-reference'
import { Hono } from 'hono'
const docs = new Hono()
// 用 Scalar 或 Swagger UI 渲染文档
docs.get(
'/docs',
Scalar({
url: 'http://localhost:8787/doc',
})
)这样做的好处:
- 内部开发仍然走 RPC 路线,前端用
hc调用,享受类型推导 - 外部文档自动生成,不需要手写 YAML
- 第三方对接方可以拿到 OpenAPI 文档,用他们自己的工具生成客户端代码
代价是路由写法从纯 Hono 切换到了 @hono/zod-openapi,需要额外引入 createRoute 的定义方式,每个路由都要显式声明 responses schema。比纯 RPC 多了一层约束,但比手写 YAML 少了很多工作。
7. 迁移考虑
从 OpenAPI 迁移到 Hono RPC
如果你的项目目前是 OpenAPI 流程,想切换到 RPC,主要工作不是改代码,而是调整工作流:
- 把 OpenAPI YAML 里的 schema 定义逐个翻译成 Zod schema。这一步可以半自动化——
openapi-zod-client能从 OpenAPI 文件生成 Zod schema - 把后端路由改成 Hono 链式写法,用
zValidator或@hono/zod-openapi注册路由 - 删除 YAML 文件和代码生成脚本
- 前端从
openapi-typescript生成的类型切换到hc<AppType>客户端
主要风险点:OpenAPI YAML 里可能有些字段在代码生成时做了特殊映射(比如字段重命名、默认值注入),这些逻辑迁移到 Zod 时需要逐个确认。
从 Hono RPC 迁移到 OpenAPI
反向迁移的场景通常出现在:项目需要对外开放 API,或者客户端开始引入非 TypeScript 语言。
- 用
hono-openapi从现有路由生成 OpenAPI 文档,作为第一步 - 如果需要更精细的控制,逐步把
c.json()路由替换为app.openapi()路由 - 生成 OpenAPI 文件后,各语言的客户端用
openapi-generator生成
这种渐进式迁移的成本比较低,因为 Hono RPC 里已有的 Zod schema 可以直接复用。
8. 性能对比
构建期
OpenAPI 的代码生成是一个独立的构建步骤。openapi-typescript 处理一份中等规模(几十个端点)的 YAML 文件通常在 100ms 以内。但如果集成到 CI/CD 里,每次接口变更都要重新生成并提交,会增加流水线复杂度。
Hono RPC 没有额外的构建步骤。类型推导完全由 TypeScript 编译器在 tsc 时完成。代价是 TypeScript 编译时间可能会增加——当路由类型非常复杂时,类型推导的耗时会上涨。实际项目中这个差异通常在百毫秒级别,不构成瓶颈。
运行期
两者的运行时开销都可以忽略不计:
- OpenAPI 生成的客户端代码就是普通的 fetch/axios 调用,没有额外开销
hc客户端底层也是 fetch,类型推导只在编译期发生,运行时代码量和不使用 RPC 时基本一致hono-openapi的/doc端点会在请求时生成 OpenAPI JSON,但通常只在开发环境访问,不影响生产环境性能
9. 工具链对比
把两边的工具做一个对照:
| 能力 | OpenAPI 工具链 | Hono RPC 工具链 |
|---|---|---|
| 接口定义 | openapi.yaml(手写或注解生成) | TypeScript 路由代码 + Zod schema |
| 类型生成 | openapi-typescript、openapi-generator | hc<AppType> 自动推导 |
| 运行时校验 | 需要额外集成(ajv、express-validator) | Zod(已集成在 zValidator 中) |
| API 文档 | Swagger UI、Redoc、Scalar | hono-openapi + Scalar |
| Mock 服务 | Prism、MSW + OpenAPI | 需要手写 Mock handler |
| 多语言客户端 | openapi-generator(50+ 语言) | 仅 TypeScript |
| 类型与实现同步 | 手动或注解驱动 | 自动(代码即契约) |
两套工具链在各自的场景里都比较成熟。选择哪套,关键看团队已有的工具链基础和协作模式。
10. 选型决策框架
面对一个具体项目,可以按以下顺序判断:
- 客户端是否只有 TypeScript? 如果不是,OpenAPI 在「多语言客户端生成」这一项上有不可替代的优势
- 接口使用者是否包含外部第三方? 如果是,OpenAPI 文档是行业通用标准,降低对接方的理解成本
- 代码是否在同一个 monorepo? 如果是,Hono RPC 的零成本类型共享可以省掉大量维护工作
- 接口变更频率如何? 如果每天多次变更,RPC 的「改代码即改契约」比维护 YAML 效率高很多
- 团队是否有 API 管理平台? 如果有,OpenAPI 和现有平台的集成成本更低
如果 1-2 命中,选 OpenAPI。如果 3-4 命中,选 Hono RPC。如果两边都有命中,考虑混合方案。
11. 混合方案:内部 RPC,外部 OpenAPI
这是很多团队实际采用的模式:
后端 Hono 路由
├── 内部接口(/api/internal/)
│ └── 前端通过 hc 客户端调用,走 RPC 类型推导
├── 外部接口(/api/public/)
│ └── 用 @hono/zod-openapi 定义,自动生成 OpenAPI 文档
└── /doc 端点
└── 对外提供 OpenAPI JSON,第三方用 openapi-generator 生成客户端内部接口不需要对外文档,前后端都在同一个仓库,RPC 的自动类型推导是最高效的选择。
外部接口需要稳定的文档和 SDK 生成能力,OpenAPI 是行业标准。用 @hono/zod-openapi 定义路由,既保留了 RPC 的类型推导,又能自动生成 OpenAPI 文档。
这种混合方案不需要二选一,也不需要维护两套独立的系统。Hono 的中间件机制让两种路由风格可以共存于同一个应用里。
延伸阅读
- 03-Hono Client使用方式 —
hc客户端的详细用法和类型推导机制 - 04-RPC类型推导原理 —
typeof route如何提取路由类型信息 - hono-openapi GitHub — 从 Hono 路由生成 OpenAPI 文档
- @hono/zod-openapi — Zod + OpenAPI 集成
- openapi-typescript — 从 OpenAPI 文件生成 TypeScript 类型
- OpenAPI 官方规范 — OpenAPI 3.1 完整规格
总结
Hono RPC 和 OpenAPI 不是替代关系,而是面向不同场景的两种接口协作模式。
OpenAPI 的核心优势是语言无关和文档标准化。当你需要为多种语言生成客户端、对外提供 API 文档、或者和公司级 API 管理平台对接时,OpenAPI 是目前最成熟的选择。
Hono RPC 的核心优势是开发效率和零维护成本。当你的技术栈是全 TypeScript、代码在同一个 monorepo、接口变更频繁时,从代码直接提取类型比维护一份独立的 YAML 文件高效得多。
两者可以共存。@hono/zod-openapi 让你在 Hono 路由里同时拿到 RPC 类型推导和 OpenAPI 文档,内部接口走 RPC,外部接口走 OpenAPI,是实践中比较常见的混合方案。
选型时不需要纠结「哪个更好」,先回答前面的五个判断问题,答案通常会自然浮现。
下一篇讲 RPC 在 AI 项目中的应用——当 LLM 工具调用(Function Calling)遇到 Hono RPC,类型推导能怎样简化 Prompt 和接口之间的映射。