Nuxt3 最佳实践03: OpenAPI 文档输出
发布于
2025年2月2日
更新于
2025年2月2日
18
分钟阅读
Vue
1. 简介
在 Nuxt 3 中,我们可以利用 Nitro 服务器内置的 OpenAPI 文档生成功能,结合 Swagger UI 或 Scalar UI 进行可视化展示。然而,该功能存在以下问题:
- 实验性功能:Nitro 的 OpenAPI 支持仍处于实验阶段,未来可能被修改或移除。
- 缺乏 API 过滤机制:所有 Server API 都会被暴露,无法通过配置排除特定 API。
- 无法与 Zod 结合:如果使用
zod定义 schema,Nitro 无法自动转换其格式到 OpenAPI,需要额外定义 JSON Schema。
因此,我更推荐采用一种更灵活的方案,该方案具备以下优势:
- 与
zod无缝集成,直接使用zodschema 生成 OpenAPI 文档。 - 支持 API 过滤,可选择性隐藏特定 API。
2. 实现方案
2.1 安装依赖
我们首先需要安装 zod 及其 OpenAPI 适配库 zod-openapi。
npm install zod zod-openapi
2.2 方案概述
主要实现步骤如下:
- 创建 OpenAPI 注册中心:用于收集 API 的相关定义。
- 提供
defineOpenAPI方法:各 API 通过该方法向注册中心注册接口信息。 - 定义 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 生成更加灵活高效。