23.07-OpenAPI文档生成

要点

  • Hono 可以从 Zod schema 自动生成 OpenAPI 3.0 文档
  • 使用 @hono/zod-openapi 扩展可以同时做参数校验和文档生成
  • 提供 Swagger UI 交互式文档界面
  • 前端可以从 OpenAPI spec 自动生成类型安全的客户端

内容

1. 安装依赖

npm install @hono/zod-openapi @hono/swagger-ui

2. 创建 OpenAPI 应用

// src/index.ts
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { SwaggerUI } from '@hono/swagger-ui'
 
const app = new OpenAPIHono()
 
// 定义路由
const route = createRoute({
  method: 'post',
  path: '/v1/chat/completions',
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            messages: z.array(z.object({
              role: z.enum(['system', 'user', 'assistant']).openapi({
                description: '消息角色',
                example: 'user',
              }),
              content: z.string().openapi({
                description: '消息内容',
                example: '你好',
              }),
            })),
            model: z.enum(['gpt-4', 'gpt-3.5-turbo']).openapi({
              description: '模型名称',
              example: 'gpt-4',
            }),
          }),
        },
      },
    },
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: z.object({
            id: z.string().openapi({ description: '请求 ID' }),
            choices: z.array(z.object({
              message: z.object({
                role: z.enum(['assistant']),
                content: z.string(),
              }),
              finish_reason: z.enum(['stop', 'length']),
            })),
            usage: z.object({
              prompt_tokens: z.number(),
              completion_tokens: z.number(),
              total_tokens: z.number(),
            }),
          }),
        },
      },
      description: '成功响应',
    },
  },
})
 
// 实现路由
app.openapi(route, async (c) => {
  const body = c.req.valid('json')
 
  // 调用 LLM
  const result = await callLLM(body)
 
  return c.json(result, 200)
})
 
// 生成 OpenAPI spec
app.doc('/doc', {
  openapi: '3.0.0',
  info: {
    title: 'AI Gateway API',
    version: '1.0.0',
    description: 'AI 网关 API 文档',
  },
  servers: [
    {
      url: 'http://localhost:8787',
      description: '开发环境',
    },
    {
      url: 'https://api.example.com',
      description: '生产环境',
    },
  ],
})
 
// 提供 Swagger UI
app.get('/swagger', SwaggerUI({ url: '/doc' }))
 
export default app

3. OpenAPI 注解

3.1 字段描述

z.object({
  name: z.string().openapi({
    description: '用户名称',
    example: 'Alice',
    minLength: 1,
    maxLength: 100,
  }),
  email: z.string().email().openapi({
    description: '邮箱地址',
    example: '[email protected]',
  }),
  age: z.number().openapi({
    description: '年龄',
    example: 25,
    minimum: 0,
    maximum: 150,
  }),
})

3.2 枚举类型

z.enum(['active', 'inactive', 'pending']).openapi({
  description: '用户状态',
  example: 'active',
})

3.3 嵌套对象

z.object({
  user: z.object({
    id: z.string(),
    name: z.string(),
  }).openapi({
    description: '用户信息',
  }),
  posts: z.array(z.object({
    id: z.string(),
    title: z.string(),
  })).openapi({
    description: '文章列表',
  }),
})

4. 复用 Schema

// src/schemas/user.ts
import { z } from '@hono/zod-openapi'
 
export const UserSchema = z.object({
  id: z.string().openapi({ description: '用户 ID' }),
  name: z.string().openapi({ description: '用户名称' }),
  email: z.string().email().openapi({ description: '邮箱' }),
})
 
export const CreateUserSchema = UserSchema.omit({ id: true })
 
export const UserListSchema = z.object({
  users: z.array(UserSchema),
  total: z.number(),
})
// src/index.ts
import { UserSchema, CreateUserSchema, UserListSchema } from './schemas/user'
 
// 获取用户
app.openapi(
  createRoute({
    method: 'get',
    path: '/api/users/:id',
    request: {
      params: z.object({
        id: z.string(),
      }),
    },
    responses: {
      200: {
        content: {
          'application/json': {
            schema: UserSchema,
          },
        },
        description: '成功',
      },
    },
  }),
  async (c) => {
    const { id } = c.req.valid('params')
    const user = await getUser(id)
    return c.json(user, 200)
  }
)
 
// 创建用户
app.openapi(
  createRoute({
    method: 'post',
    path: '/api/users',
    request: {
      body: {
        content: {
          'application/json': {
            schema: CreateUserSchema,
          },
        },
      },
    },
    responses: {
      201: {
        content: {
          'application/json': {
            schema: UserSchema,
          },
        },
        description: '创建成功',
      },
    },
  }),
  async (c) => {
    const body = c.req.valid('json')
    const user = await createUser(body)
    return c.json(user, 201)
  }
)

5. 认证配置

app.doc('/doc', {
  openapi: '3.0.0',
  info: {
    title: 'AI Gateway API',
    version: '1.0.0',
  },
  components: {
    securitySchemes: {
      BearerAuth: {
        type: 'http',
        scheme: 'bearer',
        bearerFormat: 'JWT',
        description: 'JWT 认证 token',
      },
      ApiKeyAuth: {
        type: 'apiKey',
        in: 'header',
        name: 'X-API-Key',
        description: 'API Key 认证',
      },
    },
  },
  security: [
    { BearerAuth: [] },
  ],
})

6. 分组和标签

// 用户相关
app.openapi(
  createRoute({
    method: 'get',
    path: '/api/users',
    tags: ['用户管理'],
    // ...
  }),
  // ...
)
 
// 聊天相关
app.openapi(
  createRoute({
    method: 'post',
    path: '/api/chat',
    tags: ['AI 聊天'],
    // ...
  }),
  // ...
)

7. 导出 OpenAPI spec

7.1 JSON 格式

curl http://localhost:8787/doc > openapi.json

7.2 YAML 格式

npm install js-yaml
import yaml from 'js-yaml'
 
app.get('/doc.yaml', (c) => {
  const spec = app.getOpenAPIDocument({
    openapi: '3.0.0',
    info: { title: 'AI Gateway', version: '1.0.0' },
  })
 
  return c.text(yaml.dump(spec), 200, {
    'Content-Type': 'application/x-yaml',
  })
})

8. 前端生成客户端

npx @openapitools/openapi-generator-cli generate \
  -i http://localhost:8787/doc \
  -g typescript-fetch \
  -o ./generated
// 使用生成的客户端
import { UsersApi, ChatApi } from './generated'
 
const usersApi = new UsersApi()
const user = await usersApi.getUser('123')
 
const chatApi = new ChatApi()
const response = await chatApi.createChat({
  messages: [{ role: 'user', content: '你好' }],
})

9. 实战:完整的 API 文档

// src/index.ts
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import { SwaggerUI } from '@hono/swagger-ui'
 
const app = new OpenAPIHono()
 
// 公共 Schema
const ErrorSchema = z.object({
  error: z.string().openapi({ description: '错误信息' }),
  code: z.string().openapi({ description: '错误代码' }),
})
 
// 用户路由
const getUserRoute = createRoute({
  method: 'get',
  path: '/api/users/:id',
  tags: ['用户'],
  summary: '获取用户信息',
  description: '根据 ID 获取用户详细信息',
  request: {
    params: z.object({
      id: z.string().openapi({ description: '用户 ID', example: '123' }),
    }),
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: z.object({
            id: z.string(),
            name: z.string(),
            email: z.string(),
          }),
        },
      },
      description: '成功',
    },
    404: {
      content: {
        'application/json': {
          schema: ErrorSchema,
        },
      },
      description: '用户不存在',
    },
  },
})
 
app.openapi(getUserRoute, async (c) => {
  const { id } = c.req.valid('params')
  const user = await getUser(id)
 
  if (!user) {
    return c.json({ error: 'Not found', code: 'USER_NOT_FOUND' }, 404)
  }
 
  return c.json(user, 200)
})
 
// OpenAPI 文档
app.doc('/doc', {
  openapi: '3.0.0',
  info: {
    title: 'AI Gateway API',
    version: '1.0.0',
    description: 'AI 网关 API 文档,提供用户管理、AI 聊天等功能',
    contact: {
      name: 'API Support',
      email: '[email protected]',
    },
  },
  servers: [
    { url: 'http://localhost:8787', description: '开发环境' },
    { url: 'https://api.example.com', description: '生产环境' },
  ],
  tags: [
    { name: '用户', description: '用户管理相关接口' },
    { name: 'AI 聊天', description: 'AI 聊天相关接口' },
  ],
})
 
// Swagger UI
app.get('/swagger', SwaggerUI({ url: '/doc' }))
 
export default app

10. 小结

OpenAPI 文档生成的关键点:

  1. 自动从 Zod 生成:使用 @hono/zod-openapi 同时做校验和文档生成
  2. OpenAPI 注解:使用 .openapi() 添加描述、示例、约束
  3. 复用 Schema:抽取公共 Schema,避免重复定义
  4. 认证配置:在 OpenAPI spec 中声明认证方式
  5. Swagger UI:提供交互式文档界面
  6. 客户端生成:前端可以从 OpenAPI spec 自动生成类型安全的客户端

一句话带走:

Hono + Zod OpenAPI = 参数校验 + 文档生成 + 类型推导,一次定义,三处复用。

23.07-OpenAPI文档生成 - Hono AI 项目知识体系 - AI共学社