如何在NestJS Swagger中配置统一自定义响应格式(无需为每个API创建单独响应类)
如何在NestJS中复用Swagger自定义响应格式,避免重复创建响应类
这个问题我之前也遇到过——为每个接口写单独的响应类确实太繁琐了,尤其是当所有接口都遵循统一的响应格式时。下面是我在NestJS+Swagger项目中复用响应结构的解决方案,既能保持格式统一,又能灵活定制不同接口的data部分:
1. 创建泛型基础响应类
首先定义一个泛型的基础响应类,作为所有接口响应的模板。泛型的作用是让我们可以灵活传入data部分的类型:
import { ApiProperty } from "@nestjs/swagger"; // 泛型类,T代表data字段的类型,默认是any export class SuccessResponse<T = any> { @ApiProperty({ example: 200, description: 'HTTP状态码' }) statusCode: number; @ApiProperty({ example: 'success', description: '响应状态标识' }) status: string; @ApiProperty({ description: '响应提示信息', required: false }) message?: string; @ApiProperty({ description: '响应数据', required: false }) data?: T; }
2. 定义接口专属的Data子模型
对于需要返回特定数据结构的接口(比如登录接口的令牌+用户信息),单独定义对应的子模型,不用重复写整个响应结构:
import { ApiProperty } from "@nestjs/swagger"; // 用户信息子模型 class UserInfo { @ApiProperty({ example: '张三', description: '用户名' }) name: string; @ApiProperty({ example: 'zhangsan@example.com', description: '邮箱' }) email: string; @ApiProperty({ example: '60d21b4667d0d8992e610c85', description: '用户ID' }) id: string; } // 登录返回的令牌数据模型 class AuthTokenData { @ApiProperty({ example: 3600, description: '令牌过期时间(秒)' }) expiresIn: number; @ApiProperty({ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', description: '访问令牌' }) accessToken: string; @ApiProperty({ type: UserInfo }) user: UserInfo; }
3. 在Swagger中复用基础响应类
NestJS的Swagger支持用ApiExtraModels和getSchemaPath来合并泛型模板和自定义子模型,避免重复代码。
首先在控制器上声明要用到的子模型,然后在接口的ApiOkResponse/ApiCreatedResponse中用allOf合并基础响应模板和自定义结构:
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; import { ApiOkResponse, ApiCreatedResponse, ApiExtraModels, getSchemaPath } from '@nestjs/swagger'; import { SuccessResponse } from './success.response'; import { AuthService } from './auth.service'; import { AuthRegisterLoginDto, AuthLoginDto } from './dto'; // 告诉Swagger要处理这些子模型 @ApiExtraModels(AuthTokenData, UserInfo) @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} // 登录接口示例:返回令牌+用户信息 @Post('login') @HttpCode(HttpStatus.OK) @ApiOkResponse({ description: '登录成功', schema: { allOf: [ // 引用基础响应模板 { $ref: getSchemaPath(SuccessResponse) }, // 自定义当前接口的响应细节:statusCode示例、data结构 { properties: { statusCode: { example: 200 }, status: { example: 'success' }, message: { example: '登录成功' }, data: { $ref: getSchemaPath(AuthTokenData) } } } ] } }) async login(@Body() authLoginDto: AuthLoginDto) { return this.authService.login(authLoginDto); } // 注册接口示例:可以选择不返回data,只返回状态信息 @Post('email/register') @HttpCode(HttpStatus.CREATED) @ApiCreatedResponse({ description: '用户创建成功', schema: { allOf: [ { $ref: getSchemaPath(SuccessResponse) }, { properties: { statusCode: { example: 201 }, status: { example: 'success' }, message: { example: '用户注册成功' } // 如果需要返回用户信息,取消下面的注释即可 // data: { $ref: getSchemaPath(UserInfo) } } } ] } }) async register(@Body() authRegisterLoginDto: AuthRegisterLoginDto) { return this.authService.register(authRegisterLoginDto); } }
4. 封装工具函数进一步简化代码
如果觉得每个接口都写allOf太麻烦,可以封装一个工具函数来统一生成响应配置:
import { ApiResponseOptions, getSchemaPath } from '@nestjs/swagger'; import { SuccessResponse } from './success.response'; export function buildSuccessResponse<T>(options: { description: string; statusCode: number; dataSchema?: { $ref: string }; example?: Partial<SuccessResponse<T>>; }): ApiResponseOptions { const baseSchema = { $ref: getSchemaPath(SuccessResponse) }; const customProperties: any = { statusCode: { example: options.statusCode }, status: { example: 'success' } }; // 添加自定义data结构 if (options.dataSchema) { customProperties.data = options.dataSchema; } // 合并自定义示例 if (options.example) { Object.entries(options.example).forEach(([key, value]) => { customProperties[key] = { example: value }; }); } return { description: options.description, schema: { allOf: [baseSchema, { properties: customProperties }] } }; }
之后在接口中使用这个工具函数,代码会简洁很多:
@Post('login') @HttpCode(HttpStatus.OK) @ApiOkResponse(buildSuccessResponse({ description: '登录成功', statusCode: 200, dataSchema: { $ref: getSchemaPath(AuthTokenData) }, example: { message: '登录成功' } })) async login(@Body() authLoginDto: AuthLoginDto) { return this.authService.login(authLoginDto); }
5. 确保实际返回结构与Swagger定义一致
最后别忘了在你的服务层方法中返回符合SuccessResponse结构的数据,比如:
async login(dto: AuthLoginDto): Promise<SuccessResponse<AuthTokenData>> { // 执行登录逻辑...获取token和用户信息 const tokenData = { expiresIn: 3600, accessToken: '生成的JWT令牌', user: { name: dto.email.split('@')[0], email: dto.email, id: '用户MongoDB ID' } }; return { statusCode: HttpStatus.OK, status: 'success', message: '登录成功', data: tokenData }; }
这样配置后,Swagger就能正确展示每个接口的自定义响应格式,同时你也不用为每个接口单独创建响应类了。
内容的提问来源于stack exchange,提问作者monkeyUser




