介绍
NestJs 接口统一响应封装,支持成功、错误、分页响应,封装了Swagger装饰器更友好的生成接口文档
⚠️阅前请先学习Swagger基础!
1. 定义响应码、响应信息
src/const/api.const.ts
export const API_CODES = {
OK: 200,
UNKNOWN: 99999,
USER_EXIST: 40001,
USER_NO_EXIST: 40002,
};
export const API_MSGS = {
[API_CODES.OK]: '成功',
[API_CODES.UNKNOWN]: '未知错误',
[API_CODES.USER_EXIST]: '用户已存在',
[API_CODES.USER_NO_EXIST]: '用户不存在',
};
2. 定义响应数据类
定义响应数据类,并使用@ApiProperty装饰器对属性进行描述,用于Swagger自动生成接口文档
src/dto/api.dto.ts
⚠️文件命名必须以.dto.ts结尾,否则Swagger-cli将无法解析
import { API_CODES, API_MSGS } from '../const/api.const';
import { ApiProperty } from '@nestjs/swagger';
/**
* 基础响应类
*/
export class ApiBaseRes {
@ApiProperty({ description: '状态码' })
code: number;
@ApiProperty({ description: '状态信息' })
msg: string;
}
/**
* 基础Ok响应类
*/
export class ApiBaseOkRes extends ApiBaseRes {
@ApiProperty({ default: API_CODES.OK })
code: number;
@ApiProperty({ default: API_MSGS[API_CODES.OK] })
msg: string;
}
/**
* Ok响应类
*/
export class ApiOkRes<TData = any> extends ApiBaseOkRes {
@ApiProperty({ description: '数据' })
data: TData;
}
/**
* Err响应类
*/
export class ApiErrRes extends ApiBaseRes {
@ApiProperty({ description: '错误详细' })
err?: string;
}
/**
* 分页Ok响应类
*/
export class ApiPagerOkRes<TData = any> extends ApiBaseOkRes {
@ApiProperty({ description: '分页页码', default: 1 })
page: number;
@ApiProperty({ description: '分页页大小', default: 10 })
limit: number;
@ApiProperty({ description: '分页数据总量', default: 10 })
total: number;
@ApiProperty({ description: '分页列表' })
list: TData[];
}
/**
* 分页输入参数类
*/
export class PagerDto {
@ApiProperty({ description: '分页页码', example: 1 })
page: number;
@ApiProperty({ description: '分页页大小', example: 10 })
limit: number;
}
/**
* 空模型类
*/
export class EmptyModel {}
3. 封装响应
src/utils/api.ts
import { API_MSGS, API_CODES } from '../const/api.const';
import { ApiErrRes, ApiOkRes, ApiPagerOkRes } from 'src/dto/api.dto';
interface IPagerOkOpts {
list: any[];
page: number;
limit: number;
total: number;
}
type IErrArgs = [number, string?, Error?];
/**
* 成功
*/
export class ApiOk implements ApiOkRes {
code: number = API_CODES.OK;
msg: string = API_MSGS[API_CODES.OK];
data: any;
constructor(data: any = null) {
this.data = data;
}
}
/**
* 分页成功
*/
export class ApiPagerOk implements ApiPagerOkRes {
code: number = API_CODES.OK;
msg: string = API_MSGS[API_CODES.OK];
list: any;
page: number;
limit: number;
total: number;
constructor({ list = [], page, limit: pageSize, total }: IPagerOkOpts) {
this.list = list;
this.page = page;
this.limit = pageSize;
this.total = total;
}
}
/**
* 错误
*/
export class ApiErr implements ApiErrRes {
code: number = API_CODES.UNKNOWN;
msg: string = API_MSGS[API_CODES.UNKNOWN];
err?: string;
constructor(...args: IErrArgs) {
const [code, msg, err] = args;
this.code = code;
this.msg = msg ?? API_MSGS[code];
this.err = err ? err.toString() : null;
}
}
/**
* Api响应类
*/
export class Api {
/**
* 成功
*/
static ok = (data?) => new ApiOk(data);
/**
* 分页成功
*/
static pagerOk = (opts: IPagerOkOpts) => new ApiPagerOk(opts);
/**
* 错误
*/
static err = (...args: IErrArgs) => new ApiErr(...args);
}
4. 配置Swagger
在nest-cli.json中添加如下配置
| 选项 | 描述 |
|---|---|
dtoFileNameSuffix | DTO(数据传输对象)文件后缀 |
classValidatorShim | 如果设置为 true,模块将重用class-validator验证装饰器(例如@Max(10),将添加max: 10到模式定义中) |
introspectComments | 如果设置为 true,插件将根据评论为属性生成描述和示例值 |
nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"dtoFileNameSuffix": [
".dto.ts",
".entity.ts",
".interface.ts",
".schema.ts"
],
"classValidatorShim": true,
"introspectComments": true
}
}
]
}
}
5. 封装Swagger装饰器
src/decorators/swagger.decorator.ts
import {
getSchemaPath,
ApiExtraModels,
ApiResponse,
ApiBody,
} from '@nestjs/swagger';
import { applyDecorators, Type } from '@nestjs/common';
import {
ApiErrRes,
ApiOkRes,
ApiPagerOkRes,
EmptyModel,
PagerDto,
} from 'src/dto/api.dto';
import { API_CODES, API_MSGS } from 'src/const/api.const';
/**
* 成功
*/
export const SwaggerOk = <TModel extends Type<any>>(model?: TModel) => {
const decorators = [
ApiExtraModels(ApiOkRes),
ApiExtraModels(model ?? EmptyModel),
ApiResponse({
description: API_MSGS[API_CODES.OK],
status: API_CODES.OK,
schema: {
allOf: [
{ $ref: getSchemaPath(ApiOkRes) },
{
properties: {
data: {
$ref: getSchemaPath(model ?? EmptyModel),
default: null,
},
},
},
],
},
}),
];
return applyDecorators(...decorators);
};
/**
* 分页成功
*/
export const SwaggerPagerOk = <TModel extends Type<any>>(model: TModel) => {
const decorators = [
ApiExtraModels(ApiPagerOkRes),
ApiExtraModels(model),
ApiResponse({
description: API_MSGS[API_CODES.OK],
status: API_CODES.OK,
schema: {
allOf: [
{ $ref: getSchemaPath(ApiPagerOkRes) },
{
properties: {
list: {
type: 'array',
items: { $ref: getSchemaPath(model) },
},
},
},
],
},
}),
];
return applyDecorators(...decorators);
};
/**
* 错误
*/
export const SwaggerErr = (code) => {
const decorators = [
ApiExtraModels(ApiErrRes),
ApiResponse({
description: `${API_MSGS[code]}`,
status: code,
schema: {
allOf: [
{ $ref: getSchemaPath(ApiErrRes) },
{
properties: {
code: {
default: code,
},
msg: {
default: API_MSGS[code],
},
err: {
default: null,
},
},
},
],
},
}),
];
return applyDecorators(...decorators);
};
/**
* 分页入参
*/
export const SwaggerPagerBody = <TModel extends Type<any>>(model?: TModel) => {
const decorators = [
ApiExtraModels(PagerDto),
ApiExtraModels(model ?? EmptyModel),
ApiBody({
schema: {
allOf: [
{ $ref: getSchemaPath(PagerDto) },
{ $ref: getSchemaPath(model ?? EmptyModel) },
],
},
}),
];
return applyDecorators(...decorators);
};
6. 定义interface
此处的注释必须使用此格式,否则Swagger无法生成description
src/modules/user/user.interface.ts
export class IUser {
/**
* 账号
*/
account: string;
/**
* 昵称
*/
nickname: string;
/**
* 年龄
*/
age: number;
}
7. 使用
src/modules/user/user.controller.ts
import { API_CODES } from 'src/const/api.const';
import { IUser } from './user.interface';
import { Body, Controller, Post } from '@nestjs/common';
import { UserService } from './user.service';
import { UserInfoDto } from './user.dto';
import {
SwaggerErr,
SwaggerOk,
SwaggerPagerOk,
} from 'src/decorators/swagger.decorator';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Post('info')
@SwaggerOk(IUser)
@SwaggerErr(API_CODES.USER_NO_EXIST)
info(@Body() userInfoDto: UserInfoDto) {
return this.userService.info(userInfoDto);
}
@Post('list')
@SwaggerPagerOk(IUser)
list() {
return this.userService.list();
}
}
src/modules/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { API_CODES } from 'src/const/api.const';
import { Api } from 'src/utils/api';
import { UserInfoDto } from './user.dto';
import { IUser } from './user.interface';
@Injectable()
export class UserService {
private dbUsers: IUser[] = [
{
account: '111111',
nickname: 'cbb',
age: 18,
},
{
account: '222222',
nickname: 'cbb',
age: 18,
},
{
account: '333333',
nickname: 'cbb',
age: 18,
},
{
account: '444444',
nickname: 'cbb',
age: 18,
},
{
account: '555555',
nickname: 'cbb',
age: 18,
},
];
/**
* 查询用户信息
*/
info(userInfoDto: UserInfoDto) {
const user = this.dbUsers.find(
(user) => user.account === userInfoDto.account,
);
if (user) {
// 成功 - 查询到用户
return Api.ok(user);
} else {
// 错误 - 查询的用户不存在
return Api.err(API_CODES.USER_NO_EXIST);
}
}
/**
* 用户列表
*/
list() {
return Api.pagerOk({
list: this.dbUsers,
page: 1,
limit: 10,
total: this.dbUsers.length,
});
}
}
}