类型安全是前后端协作的老大难问题。接口文档更新滞后、字段类型对不上、联调时才发现传参错误——这些场景开发者再熟悉不过。Hono RPC提供了一种新思路:不生成代码,直接共享类型。

本文将用完整步骤演示,如何在Monorepo中搭建一个Hono后端与React前端,实现端到端的类型同步。

打开网易新闻 查看精彩图片

环境准备

新建项目目录,初始化pnpm工作区:

mkdir hono-rpc-and-react-monorepo
cd hono-rpc-and-react-monorepo
npm i -g pnpm@latest
pnpm init

创建pnpm-workspace.yaml,声明两个包:

packages:
- 'api'
- 'web'

后端:Hono API

用官方脚手架创建Hono项目:

pnpm create hono@latest api

修改api/package.json,调整三项关键配置:

{
"name": "@repo/api",
"type": "module",
"main": "src/index.ts",
...
}

name改为@repo/api以便workspace引用;type指定ES模块;main指向源码入口,让前端能直接导入类型。

核心在api/src/index.ts。关键操作:把路由挂载到Hono实例本身,而非分离的router对象——这样TypeScript才能完整推断出AppType。

import { serve } from '@hono/node-server'
import { Hono } from 'hono'

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

export type AppType = typeof app

serve({...}, (info) => {
console.log(`Server is running on http://localhost:${info.port}`)
})

注意路由改为/api前缀,为后续代理做准备。

前端:React + Vite

创建Vite项目:

pnpm create vite

安装依赖:

pnpm i hono

在web/package.json中引入后端包:

"devDependencies": {
"@repo/api": "workspace:*",
...
}

pnpm i安装后,配置开发代理。修改web/vite.config.ts:

export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
})

所有以/api开头的请求自动转发到3000端口。

在web/src/App.tsx中创建客户端:

import { hc } from 'hono/client'
import type { AppType } from '@repo/api'

const client = hc('/')

function App() {
const [count, setCount] = useState('')
useEffect(() => {
const fetchData = async () => {
const res = await client.api.$get()
if (res.ok) {
const data = await res.text()
setCount(data)
}
}
fetchData()
}, [])
...
}

hc函数接收AppType泛型,client.api.$get()完全继承了后端的路由结构和返回类型。修改后端接口后,前端调用处会立即触发类型检查。

一键启动

根目录package.json添加:

"scripts": {
"dev": "pnpm run --parallel dev"
}

pnpm dev同时启动前后端。浏览器访问前端地址,即可看到从Hono后端获取的"Hello Hono!"文本。

类型同步的边界

Hono RPC的局限也值得关注:它依赖TypeScript编译时的类型系统,运行时并无额外校验;跨语言场景(如后端改为Go)无法沿用;复杂嵌套类型的序列化仍需手动确保一致。

但在全TypeScript栈的项目中,这种零代码生成的方案显著降低了维护成本——没有OpenAPI生成步骤,没有版本对齐会议,类型即契约。