Hono 工程化实战 07:项目模板、Monorepo 和 CI 流程
Hono 工程化实战 07:项目模板、Monorepo 和 CI 流程
一个 Hono 项目能不能长期维护,和第一天的目录结构关系很大。
小 demo 可以一个 index.ts 写完。
但真实项目很快会长出:
1 2 3 4 5 6 7 8 9 10
| 路由 middleware service repository schema 测试 脚本 配置 前端客户端 部署文件
|
如果一开始没有边界,后面就会变成:
1 2 3 4 5
| 所有逻辑塞 routes 所有类型到处复制 前后端各写一份接口定义 测试不知道怎么 mock CI 只会 npm install
|
这篇文章讲 Hono 项目如何搭一个可维护的工程模板,包括目录结构、monorepo、共享类型、测试和 CI。
技术栈快照时间:2026-06-23。Hono RPC、Testing Helper、各运行时部署方式会更新,生产使用前以官方文档为准。
一、先从单应用结构开始
如果只有一个 API,可以先这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| src/ app.ts index.ts routes/ posts.ts auth.ts middleware/ auth.ts request-id.ts schemas/ post.ts auth.ts services/ post-service.ts auth-service.ts repositories/ post-repository.ts user-repository.ts errors/ app-error.ts config/ runtime-config.ts tests/ routes/ services/
|
app.ts 负责组装 Hono:
1 2 3 4 5 6 7 8 9 10 11 12
| import { Hono } from 'hono' import { authRoutes } from './routes/auth' import { postRoutes } from './routes/posts'
export function createApp(deps: AppDeps) { const app = new Hono()
app.route('/api/auth', authRoutes(deps)) app.route('/api/posts', postRoutes(deps))
return app }
|
index.ts 负责运行时入口。
这样测试可以直接 import createApp(),部署入口也清楚。
二、routes 不要写成万能层
很多项目最大的问题是 route 太胖。
错误方向:
1 2 3 4 5 6 7 8
| app.post('/api/posts', async (c) => { })
|
route 层应该主要做:
1 2 3 4
| 读取请求 调用 schema 校验 调用 service 转换响应
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| export function postRoutes(deps: AppDeps) { const app = new Hono()
app.post('/', async (c) => { const input = await c.req.json() const post = await deps.posts.create(input)
return c.json({ data: post }, 201) })
return app }
|
业务规则放 service。
数据访问放 repository。
这样测试和替换实现都容易。
三、什么时候用 Monorepo
如果你只有一个后端项目,不需要 monorepo。
但如果你有:
1 2 3 4 5 6
| web 前端 Hono API 共享类型 后台 worker 脚本工具 组件库
|
monorepo 会更舒服。
结构:
1 2 3 4 5 6 7 8
| apps/ web/ api/ worker/ packages/ shared/ config/ api-client/
|
好处:
1 2 3 4 5
| 前后端共享类型 统一 lint/test/build 统一依赖版本 Hono RPC 类型更容易导出 重构跨应用更方便
|
缺点:
不要为了看起来高级而上 monorepo。
但前后端都 TypeScript,而且接口类型共享强,monorepo 很适合 Hono。
四、共享类型怎么放
共享类型应该放在稳定包里:
1 2 3 4 5
| packages/shared/src/ types/ constants/ errors/ schemas/
|
比如错误码:
1 2 3 4 5 6 7
| export const ErrorCodes = { AUTH_REQUIRED: 'AUTH_REQUIRED', PERMISSION_DENIED: 'PERMISSION_DENIED', POST_NOT_FOUND: 'POST_NOT_FOUND', } as const
export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes]
|
前端和后端都引用同一份。
不要前端手写一份:
1 2 3
| if (error.code === 'POST_NOT_FOUND') { }
|
后端又写另一份。
重复定义就是漂移的开始。
五、Hono RPC 在 monorepo 里很舒服
如果 API 和前端在同一仓库,可以导出 Hono app 类型:
1 2 3 4 5 6
| const routes = app .route('/api/posts', postRoutes(deps)) .route('/api/auth', authRoutes(deps))
export type AppType = typeof routes
|
前端:
1 2 3 4
| import { hc } from 'hono/client' import type { AppType } from '@my-app/api'
export const api = hc<AppType>(import.meta.env.VITE_API_BASE_URL)
|
这样路径和参数都有类型提示。
但要注意:
可以用 tsconfig、package exports、构建脚本来约束。
共享类型很香,但边界要守住。
六、CI 至少做四件事
一个 Hono 项目的 CI,不要只有 build。
至少:
1 2 3 4
| typecheck lint test build
|
package scripts:
1 2 3 4 5 6 7 8 9
| { "scripts": { "typecheck": "tsc --noEmit", "lint": "eslint .", "test": "vitest run", "build": "tsc", "ci": "pnpm typecheck && pnpm lint && pnpm test && pnpm build" } }
|
如果项目暂时没有 lint,也至少要有:
TypeScript 项目不跑 typecheck 就合并代码,有点浪费 TypeScript。
七、CI 里加接口 smoke test
构建成功不代表服务能用。
部署到预览环境后,最好跑几个 smoke test:
1 2 3 4
| GET /health GET /api/version POST /api/auth/login with test account GET /api/me
|
可以写一个简单脚本:
1 2 3 4 5 6 7 8 9
| const baseUrl = process.env.API_BASE_URL!
const health = await fetch(`${baseUrl}/health`)
if (!health.ok) { throw new Error(`health check failed: ${health.status}`) }
console.log('smoke test passed')
|
CI 分两阶段:
1 2
| 代码阶段:typecheck/test/build 部署阶段:deploy preview + smoke test
|
这能挡住很多“构建成功但服务不可用”的问题。
八、环境分支和部署环境
不要把所有环境都混在一起。
常见模型:
1 2 3
| feature branch -> preview develop/main -> staging tag/release -> production
|
或者简单一点:
1 2
| main -> production pull request -> preview
|
关键是:
1 2 3 4
| 每个环境有独立配置 每个环境有独立数据库或命名空间 生产 secret 不给 preview preview 不写生产数据
|
Hono app 本身不复杂。
复杂的是它连接的资源。
CI/CD 要把这些环境边界守住。
九、脚手架要有 README
项目模板不是只给机器看的。
也要给人看。
README 至少写:
1 2 3 4 5 6 7
| 项目结构 本地启动 环境变量 数据库迁移 测试命令 部署命令 常见问题
|
比如:
1 2 3 4 5 6 7
| ## Local Development
1. pnpm install 2. cp .env.example .env 3. pnpm db:migrate 4. pnpm dev 5. pnpm test
|
新人能不能半小时跑起来,是工程质量的一个很真实指标。
十、不要过早模板化
有些团队一上来就做内部脚手架。
里面塞满:
1 2 3 4 5 6 7 8 9
| 认证 权限 日志 ORM 队列 缓存 OpenAPI GraphQL 微服务
|
结果项目还没开始,模板已经很重。
我的建议:
比如:
1 2 3 4 5 6 7
| Hono app factory 统一错误结构 requestId config 校验 Vitest 示例 README CI 基础流程
|
业务相关的东西不要急着塞进模板。
模板越轻,越容易被团队真正使用。
十一、推荐的最小 Hono 工程模板
我会这样开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| src/ app.ts index.ts middleware/ request-id.ts error-handler.ts config/ runtime-config.ts routes/ health.ts errors/ app-error.ts tests/ health.test.ts .env.example package.json tsconfig.json README.md
|
CI:
1 2 3 4
| pnpm install --frozen-lockfile pnpm typecheck pnpm test pnpm build
|
然后随着业务增长,再加:
1 2 3 4 5
| schemas services repositories workers packages/shared
|
结构要服务于复杂度。
不是一开始就把未来三年的复杂度摆在第一天。
Hono 的轻,应该体现在工程模板也轻。
但轻不等于散。
一个好的 Hono 模板,应该让团队快速开始,同时给后续增长留出清晰边界。