Fastify 最佳实践 #5: Swagger 文档生成
在开发服务端API时,维护一份准确、实时、易于理解的API文档是提升团队协作效率和外部开发者体验的关键。手写文档不仅耗时,还极易出现代码与文档不一致的情况。幸运的是,在Fastify生态中,我们可以通过 @fastify/swagger 和 @fastify/swagger-ui 这两个利器,将我们的代码(特别是Zod Schema)直接转化为专业、可交互的Swagger文档,实现“代码即文档”。
首先,我们需要安装相关依赖:
npm install @fastify/swagger @fastify/swagger-ui
配置 Swagger 插件
接下来,我们需要在 Fastify 实例上注册这两个插件。在一个独立的插件文件(例如 plugins/swagger.ts)中进行配置是很好的实践。
这里的核心是让 @fastify/swagger 能够理解我们在上一篇文章中大力推崇的 Zod Schema。
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';
import fastify, { FastifyInstance } from 'fastify';
import { jsonSchemaTransform } from 'fastify-type-provider-zod';
export const setupSwagger = async (server: FastifyInstance) => {
// 注册 swagger 核心插件
await server.register(swagger, {
// 关键:让 swagger 支持 Zod schema
transform: jsonSchemaTransform,
openapi: {
openapi: '3.0.0', // OpenAPI 版本
info: {
// 文档基本信息
title: 'Fastify 最佳实践项目 API',
description: '这是一个示例项目的API文档',
version: '0.1.0',
},
servers: [
// API 服务器列表
{
url: `http://127.0.0.1:8080`,
description: '开发服务器',
},
],
tags: [
// API 分类标签
{ name: 'user', description: '用户相关API' },
{ name: 'health', description: '健康检查API' },
],
// 可选:添加全局安全认证配置
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
security: [
{
bearerAuth: [],
},
],
},
});
// 注册 swagger UI 插件,用于提供可视化文档界面
await server.register(swaggerUi, {
routePrefix: '/doc', // swagger 文档访问路径
uiConfig: {
docExpansion: 'full',
deepLinking: false,
},
uiHooks: {
onRequest: function (_request: any, _reply: any, next: any) {
next();
},
preHandler: function (_request: any, _reply: any, next: any) {
next();
},
},
staticCSP: false,
transformSpecificationClone: true,
logLevel: 'silent',
});
};
jsonSchemaTransform 的作用
你可能已经注意到,这里的关键是 transform: jsonSchemaTransform 配置。这个从 fastify-type-provider-zod 导入的函数,其作用就是一座桥梁,它能自动将你在路由中定义的 Zod Schema 转换成 Swagger(OpenAPI 规范)所要求的 JSON Schema 格式。没有它,Swagger 将无法理解我们的 Zod 类型定义。
在路由中添加文档信息
完成插件配置后,我们还需要在每个API路由的 schema 选项中提供一些元数据,Swagger 会读取这些信息来生成文档。
让我们来看一个具体的例子:
import { FastifyInstance } from 'fastify';
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod';
const routes = async (fastify: FastifyInstance) => {
fastify.withTypeProvider<ZodTypeProvider>().get(
'/user/:id', // API URL
{
schema: {
summary: '获取单个用户信息',
description: '根据用户ID查询详细信息。',
tags: ['user'], // API 分类
params: z.object({
id: z.string().uuid().describe('用户唯一ID (UUID格式)'), // Zod的 `.describe()` 方法会被自动转换成字段描述
}),
response: {
200: z
.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
})
.describe('成功响应'),
404: z
.object({
message: z.string(),
})
.describe('用户未找到时的响应'),
},
// 表示此接口需要 bearerAuth 认证
security: [{ bearerAuth: [] }],
},
},
async (req, reply) => {
// ... 业务逻辑 ...
const user = { id: req.params.id, name: 'Millet', email: 'millet@example.com' };
return reply.status(200).send(user);
},
);
};
export default routes;
总结
通过上述配置,我们不仅为项目生成了专业、可交互的 API 文档,更重要的是,这份文档是与我们的代码完全同步的 “活文档”。每当你的 Zod Schema 发生变化,文档就会自动更新,彻底告别了因手写文档而导致代码与文档不一致的烦恼。这大大提升了开发效率和 API 的可维护性,是现代服务端开发中不可或缺的一环。
在我的开源项目中,我将关于 Swagger 的配置封装在了一个独立的插件 (04.swagger.plugin.ts) 中,并通过 fastify-autoload 自动加载,欢迎参考: