log
码中赤兔

Nuxt3 最佳实践03: OpenAPI 文档输出

发布于 2025年2月2日
更新于 2025年2月2日
18 分钟阅读
Vue

1. 简介

在 Nuxt 3 中,我们可以利用 Nitro 服务器内置的 OpenAPI 文档生成功能,结合 Swagger UI 或 Scalar UI 进行可视化展示。然而,该功能存在以下问题:

  1. 实验性功能:Nitro 的 OpenAPI 支持仍处于实验阶段,未来可能被修改或移除。
  2. 缺乏 API 过滤机制:所有 Server API 都会被暴露,无法通过配置排除特定 API。
  3. 无法与 Zod 结合:如果使用 zod 定义 schema,Nitro 无法自动转换其格式到 OpenAPI,需要额外定义 JSON Schema。

因此,我更推荐采用一种更灵活的方案,该方案具备以下优势:

  • zod 无缝集成,直接使用 zod schema 生成 OpenAPI 文档。
  • 支持 API 过滤,可选择性隐藏特定 API。

2. 实现方案

2.1 安装依赖

我们首先需要安装 zod 及其 OpenAPI 适配库 zod-openapi

npm install zod zod-openapi

2.2 方案概述

主要实现步骤如下:

  1. 创建 OpenAPI 注册中心:用于收集 API 的相关定义。
  2. 提供 defineOpenAPI 方法:各 API 通过该方法向注册中心注册接口信息。
  3. 定义 OpenAPI 生成 API:该 API 读取注册中心数据并生成 OpenAPI JSON 文档。

2.3 创建 OpenAPI 注册中心

server/utils 目录下创建 openapi-registry.ts,用于存储和管理 API 定义信息。

// openapi-registry.ts
import z from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';

extendZodWithOpenApi(z);

export type OpenAPIEndpoint = {
  /** 接口标签 */
  tags: string[];
  /** 接口路径,如 '/api/users' */
  path: string;
  /** 请求方法,如 'get' 或 'post' */
  method: string;
  /** 接口操作ID */
  operationId?: string;
  /** 接口描述 */
  summary?: string;
  /** 接口详细描述 */
  description?: string;
  /** query string parameter */
  querySchema?: z.ZodObject<any, any>;
  /** path parameter */
  pathParamsSchema?: z.ZodObject<any, any>;
  /** request body */
  bodySchema?: z.ZodObject<any, any>;
  /** response body */
  responseSchema?: z.ZodType<any, any>;
};

// 内部存储接口注册信息
const registry: OpenAPIEndpoint[] = [];

/**
 * 注册一个 OpenAPI 接口
 * @param data OpenAPI 接口数据
 */
export function defineOpenAPI(data: OpenAPIEndpoint) {
  registry.push(data);
}

/**
 * 获取已注册的接口数据
 */
export function getOpenAPIRegistry() {
  return registry;
}

2.4 在 Server API 中定义 OpenAPI 信息

server/api/student.post.ts 里定义 API,并注册 OpenAPI 信息。

// server/api/student.post.ts
import { defineEventHandler, readValidatedBody } from 'h3';
import z from 'zod';

const bodySchema = z.object({
  name: z.string().min(1).max(100).describe('学生姓名'),
  address: z.string().min(10).max(100).describe('学生地址'),
});

const responseSchema = z.object({
  message: z.string(),
});

defineOpenAPI({
  tags: ['学生'],
  path: '/api/student',
  method: 'post',
  operationId: 'student-add',
  summary: '添加学生',
  description: '添加学生信息',
  bodySchema,
  responseSchema,
});

export default defineEventHandler(async (event) => {
  const params = await readValidatedBody(event, bodySchema.parse);
  console.log(params);
  return { message: 'success' };
});

2.5 生成 OpenAPI JSON 文档

创建 server/api/openapi.json.dev.ts 以动态生成 OpenAPI 文档。

// openapi.json.dev.ts
import { defineEventHandler } from 'h3';
import { ZodOptional } from 'zod';
import { createSchema } from 'zod-openapi';

export default defineEventHandler(() => {
  const endpoints = getOpenAPIRegistry();
  const paths: Record<string, any> = {};

  endpoints.forEach((ep) => {
    // 如果该路径还未初始化,则先初始化
    if (!paths[ep.path]) {
      paths[ep.path] = {};
    }
    const method = ep.method.toLowerCase();

    // Query String Parameters
    const queryParameters = ep.querySchema
      ? Object.entries(ep.querySchema.shape).map(([name, schema]) => {
          return {
            name,
            in: 'query',
            required: !(schema instanceof ZodOptional),
            ...createSchema(schema as any),
          };
        })
      : [];

    // Path Parameters
    const pathParameters = ep.pathParamsSchema
      ? Object.entries(ep.pathParamsSchema.shape).map(([name, schema]) => {
          return {
            name,
            in: 'path',
            required: true,
            ...createSchema(schema as any),
          };
        })
      : [];

    const parameters = [...pathParameters, ...queryParameters];

    // Request Body
    const requestBody = ep.bodySchema
      ? {
          content: {
            'application/json': createSchema(ep.bodySchema),
          },
        }
      : undefined;

    // 构造响应体
    const responses = ep.responseSchema
      ? {
          200: {
            description: '成功响应',
            content: {
              'application/json': createSchema(ep.responseSchema),
            },
          },
        }
      : {
          200: { description: '成功响应' },
        };

    paths[ep.path][method] = {
      tags: ep.tags,
      operationId: ep.operationId || `${method}_${ep.path}`,
      summary: ep.summary,
      description: ep.description,
      parameters,
      ...(requestBody ? { requestBody } : {}),
      responses,
    };
  });

  // 这里定义一些OpenAPI的基本信息
  const openApiDoc = {
    openapi: '3.0.0',
    info: {
      title: 'My API test',
      version: '1.0.0',
      description: 'This is a test API',
    },
    servers: [
      {
        url: 'http://localhost:3000',
      },
    ],
    tags: [
      {
        name: '学生',
        description: '学生相关操作',
      },
    ],
    paths,
  };

  return openApiDoc;
});

注意:在 Nitro 中,文件名以 .dev.ts 结尾的 API 仅在开发环境可用,不会被包含进生产构建。

2.6 访问 OpenAPI 文档

完成配置后,启动 Nuxt 服务器,并访问以下 URL 获取 OpenAPI 文档:

http://localhost:<port>/api/openapi.json

你可以将该 JSON 文件与 Swagger UI 或其他 OpenAPI 工具结合使用,实现可视化 API 文档。

3. 总结

本方案通过 zod-openapi 结合 defineOpenAPI 机制,实现了 自动化 API 文档生成,同时具备 zod 集成API 过滤 特性,使 OpenAPI 生成更加灵活高效。


上一篇文章:Nuxt3 最佳实践02: Request 请求体验证

关于

分享技术见解、经验和思考的个人博客

联系方式

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 码中赤兔. 版权所有