Skip to content

课 1 · Hono 搭建 TypeScript API ​

本课目标

理解为什么 Agent 项目需要 API 化,用 Hono 搭建类型安全的 TypeScript API 服务。课后你会拿到一个能跑的 apps/api,基础项目的核心能力可以通过 HTTP 调用。

先掌握一条主线:CLI 能做的,API 都能做——只是改成了别人能调用的形式。

基础课里的 Agent 是 CLI 工具:你在终端输入问题,它在终端打印答案。这一课要把它变成一个 HTTP API 服务,让前端页面、其他服务甚至 curl 都能调用它。

为什么需要 API 化 ​

CLI 工具有三个现实限制:

限制原因API 化后解决
只能一个人用进程绑定终端多个客户端可以同时发请求
状态不能共享每次启动独立进程会话、缓存可以持久化
前端无法调用没有网络接口标准 HTTP 接口,任何客户端都能访问

进阶课目标不是"更复杂的 Demo",而是把 Agent 能力变成可服务化的系统。API 层是这个系统的入口。

Hono 框架选型 ​

为什么选 Hono 而不是 Express 或 Fastify?

Hono 的核心优势是类型贯穿:路由参数、请求体、响应都有类型,不需要额外写类型断言。

项目初始化 ​

bash
# 在 advanced/project 目录下
pnpm create hono apps/api
# 选 Node.js 模板

安装依赖:

bash
cd apps/api
pnpm add hono @hono/zod-validator zod
pnpm add -D tsx @types/node

基础路由结构 ​

typescript
// apps/api/src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { chatRouter } from './routes/chat'
import { docsRouter } from './routes/docs'

const app = new Hono()

// 中间件
app.use('*', logger())
app.use('*', cors())

// 健康检查
app.get('/health', (c) => c.json({ status: 'ok' }))

// 路由挂载
app.route('/api/chat', chatRouter)
app.route('/api/docs', docsRouter)

export default app

路由设计原则 ​

API 层和 Agent 层的关键区分:

typescript
// ✅ API 层做的事:接收请求、校验参数、调用业务逻辑、返回响应
app.post('/api/chat', zValidator('json', chatSchema), async (c) => {
  const { message, sessionId } = c.req.valid('json')
  const answer = await agentService.ask(message, sessionId) // 业务逻辑在这里
  return c.json({ answer })
})

// ❌ 不要在路由里直接写 Agent 逻辑
app.post('/api/chat', async (c) => {
  const { message } = await c.req.json()
  const result = await generateText({ model, messages: [...] }) // 这该放 service 层
  return c.json({ result })
})

API 层只做三件事:接收 → 校验 → 分发。

参数校验 ​

用 @hono/zod-validator 在路由层统一校验:

typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const chatSchema = z.object({
  message: z.string().min(1).max(2000),
  sessionId: z.string().uuid().optional(),
})

chatRouter.post('/', zValidator('json', chatSchema), async (c) => {
  const { message, sessionId } = c.req.valid('json') // 已经过校验,类型安全
  // ...
})

校验失败时 Hono 会自动返回 400,不需要手写 try-catch。

统一错误处理 ​

typescript
// apps/api/src/middleware/error.ts
import type { ErrorHandler } from 'hono'

export const errorHandler: ErrorHandler = (err, c) => {
  console.error('[API Error]', err)

  if (err.message === 'RATE_LIMIT_EXCEEDED') {
    return c.json({ error: '请求过于频繁,请稍后再试' }, 429)
  }

  return c.json({ error: '内部错误' }, 500)
}

在 index.ts 挂载:

typescript
app.onError(errorHandler)

本节产物 ​

apps/api/
  src/
    index.ts          # 应用入口、中间件配置
    routes/
      chat.ts         # 问答路由
      docs.ts         # 文档路由(导入、列表)
    services/
      agent.ts        # Agent 业务逻辑(从基础项目迁移)
    middleware/
      error.ts        # 统一错误处理
  package.json

课堂实作 ​

  1. 初始化 apps/api 项目
  2. 实现 GET /health 和 POST /api/chat
  3. 用 curl 或 HTTPie 验证接口
bash
curl -X POST http://localhost:3000/api/chat \
  -H 'Content-Type: application/json' \
  -d '{"message": "你好,介绍一下这个知识库"}'

并入项目 ​

把基础项目 basic/project/src/agent.ts 里的问答逻辑迁移到 apps/api/src/services/agent.ts,API 路由调用 service,不直接持有 Agent 逻辑。

面试追问 ​

API 层和 Agent 层怎么解耦?

API 层负责 HTTP 协议细节(路由、校验、响应格式),Agent 层负责业务逻辑(工具调用、RAG、上下文管理)。两层通过 service 对象交互,互相不感知对方的实现细节。改 API 框架不影响 Agent,改 Agent 逻辑不影响路由。

为什么要在 API 层做参数校验而不是在 Agent 层?

Agent 层属于核心业务逻辑,应该假设入参是合法的。如果校验放在 Agent 层,每次被调用(HTTP、CLI、测试)都要重复处理格式问题。API 层是系统边界,边界处做校验符合"防御性编程"原则。

面向前端工程师和独立开发者的 AI 应用工程课程