NestJS build blog system (6) – Use Swagger to generate documents

In the previous example, we implemented curd of a module completely, and all this time we were typing addresses in Postman or other tools repeatedly. It was really uncomfortable, especially during team development, the fragmentation of the interface was maddeningly frustrating, so we had to write documentation. I used YAPI at first. But later, I thought it was still troublesome and needed handwritten documents, so I found Swagger again, but the Swagger document was really ugly and difficult to use, and finally became Swagger + YAPI or Swagger + Postman

Open dry

Install dependencies

Express: YARN add @nestjs/ Swagger Swagger-uI-Express fastify: yarn add @nestjs/swagger fastify-swagger

Configuration swagger

// src/main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { TransformInterceptor } from './interceptor/transform.interceptor';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe())
  app.useGlobalInterceptors(new TransformInterceptor())
  app.useGlobalFilters(new HttpExceptionFilter())

  const options = new DocumentBuilder()
    .setTitle('blog-serve')
    .setDescription('Interface Document')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('swagger-doc', app, document);

  await app.listen(3000);
}
bootstrap();
Copy the code

Localhost :3000/swagger-doc

But there is only interface, no other information, to the DTO add input information

The ginseng

The input parameter description in the document can be modified in the DTO file

// src/modules/article/dto/id.dto.ts

import { IsNotEmpty, Matches } from "class-validator";
import { regPositive } from "src/utils/regex.util";
import { ApiProperty } from "@nestjs/swagger";

export class IdDTO {
  @ApiProperty({
    description: 'the article id'.example: 1,})@Matches(regPositive, { message: 'Please enter a valid ID' })
  @IsNotEmpty({ message: 'ID cannot be null' })
  readonly id: number
}
Copy the code
// src/modules/article/dto/list.dto.ts

import { IsOptional, Matches } from "class-validator";
import { regPositiveOrEmpty } from "src/utils/regex.util";
import { ApiProperty } from "@nestjs/swagger";

export class ListDTO {
  @ApiProperty({
    description: 'What page?'.example: 1.required: false,})@IsOptional(a)@Matches(regPositiveOrEmpty, { message: 'Page cannot be less than 0' })
  readonlypage? :number;

  @ApiProperty({
    description: 'Number of data items per page'.example: 10.required: false,})@IsOptional(a)@Matches(regPositiveOrEmpty, { message: 'pageSize cannot be less than 0' })
  readonlypageSize? :number;
}
Copy the code
// src/modules/article/dto/article-create.dto.ts

import { IsNotEmpty } from "class-validator";
import { ApiProperty } from "@nestjs/swagger";

export class ArticleCreateDTO {
  @ApiProperty({
    description: 'Article Title'.example: 'ah! Beautiful Sea ',})@IsNotEmpty({ message: 'Please enter article title' })
  readonly title: string;

  @ApiProperty({
    description: 'Article Description/Introduction'.example: 'I tell you about the beautiful sea.',})@IsNotEmpty({ message: 'Please enter article description' })
  readonly description: string;

  @ApiProperty({
    description: 'Article content'.example: 'ah! Fair sea, thou art so fair. ',})@IsNotEmpty({ message: 'Please enter the content of the article' })
  readonly content: string;
}
Copy the code
// src/modules/article/dto/article-edit.dto.ts

import { IsNotEmpty, IsOptional } from "class-validator";
import { IdDTO } from "./id.dto";
import { ApiProperty } from "@nestjs/swagger";

export class ArticleEditDTO extends IdDTO {
  @ApiProperty({
    description: 'Article Title'.example: 'ah! Beautiful Sea '.required: false,})@IsOptional(a)@IsNotEmpty({ message: 'Please enter article title' })
  readonlytitle? :string;

  @ApiProperty({
    description: 'Article Description/Introduction'.example: 'I tell you about the beautiful sea.'.required: false,})@IsOptional(a)@IsNotEmpty({ message: 'Please enter article description' })
  readonlydescription? :string;

  @ApiProperty({
    description: 'Article content'.example: 'ah! Fair sea, thou art so fair. '.required: false,})@IsOptional(a)@IsNotEmpty({ message: 'Please enter the content of the article' })
  readonlycontent? :string;
}
Copy the code

The response

We didn’t have a return format at the time of development,

But we actually have this format in Section 4:

/ / list
{
  code: 200.data: {
    list: {... }pagination: {
      page: 1.pageSize: 10.pages: 10.total: 100,}},message: 'Request successful'
}

/ / details
{
  code: 200.data: {
    info: {...}
  },
  message: 'Request successful'
}
Copy the code

Here we create a couple of files

// src/modules/article/vo/article-base.vo.ts

import { ApiProperty } from '@nestjs/swagger'

class ArticleBaseItem {
  @ApiProperty({ description: 'the article id'.example: 1 })
  id: number;

  @ApiProperty({ description: 'Creation time'.example: '2021-07-03' }) 
  createTime: Date

  @ApiProperty({ description: 'Update Time'.example: '2021-07-03' }) 
  updateTime: Date

  @ApiProperty({ description: 'Article Title'.example: 'Article Title' }) 
  title: string;

  @ApiProperty({ description: 'Article Description'.example: 'Article Description' }) 
  description: string;
}

export class ArticleListItem extends ArticleBaseItem {}

export class ArticleInfoItem extends ArticleBaseItem {
  @ApiProperty({ description: 'Article content'.example: 'Article content' }) 
  content: string;
}
Copy the code
// src/modules/article/vo/article-list.vo.ts

import { ApiProperty } from "@nestjs/swagger";

class SimpleInfo {
  @ApiProperty({ description: 'the article id'.example: 1 })
  id: number;

  @ApiProperty({ description: 'Creation time'.example: '2021-07-03' }) 
  createTime: Date

  @ApiProperty({ description: 'Update Time'.example: '2021-07-03' }) 
  updateTime: Date

  @ApiProperty({ description: 'Article Title'.example: 'Article Title' }) 
  title: string;

  @ApiProperty({ description: 'Article Description'.example: 'Article Description' }) 
  description: string;
}

class Pagination {
  @ApiProperty({ description: 'What page?'.example: 1 })
  page: number

  @ApiProperty({ description: 'Number of items per page'.example: 10 })
  pageSize: number

  @ApiProperty({ description: 'Total pages'.example: 10 })
  pages: number

  @ApiProperty({ description: 'Total number'.example: 100 })
  total: number

}

export class ArticleListVO {
  @ApiProperty({ type: SimpleInfo, isArray: true })
  list: Array<SimpleInfo>

  @ApiProperty({ type: () = > Pagination })
  pagination: Pagination
}

export class ArticleListResponse {
  @ApiProperty({ description: 'Status code'.example: 200,})code: number

  @ApiProperty({ description: 'data'.type: () = > ArticleListVO, example: ArticleListVO, })
  data: ArticleListVO

  @ApiProperty({ description: 'Request result information'.example: 'Request successful' })
  message: string
} 
Copy the code
// src/modules/article/vo/article-info.vo.ts

import { ApiProperty } from "@nestjs/swagger";

class Info {
  @ApiProperty({ description: 'the article id'.example: 1 })
  id: number;

  @ApiProperty({ description: 'Creation time'.example: '2021-07-03' }) 
  createTime: Date

  @ApiProperty({ description: 'Update Time'.example: '2021-07-03' }) 
  updateTime: Date

  @ApiProperty({ description: 'Article Title'.example: 'Article Title' }) 
  title: string;

  @ApiProperty({ description: 'Article Description'.example: 'Article Description' }) 
  description: string;

  @ApiProperty({ description: 'Article content'.example: 'Article content' }) 
  content: string;
}

export class ArticleInfoVO {
  @ApiProperty({ type: Info })
  info: Info
}

export class ArticleInfoResponse {
  @ApiProperty({ description: 'Status code'.example: 200,})code: number

  @ApiProperty({ description: 'data'.type: () = > ArticleInfoVO, example: ArticleInfoVO, })
  data: ArticleInfoVO

  @ApiProperty({ description: 'Request result information'.example: 'Request successful' })
  message: string
} 
Copy the code

Every class we create in these two pages will end up as Swagger’s Schema, so any classes with the same name will cause our data to be overwritten (even if they are not in the same file, so we can continue to optimize later.

Now rewrite the article. Controller to add the type of return value to the method and the sample response on Swagger

// src/modules/article/article.controller.ts

import { Controller, Body, Query, Get, Post } from '@nestjs/common';
import { ArticleService } from './article.service';
import { ArticleCreateDTO } from './dto/article-create.dto';
import { ArticleEditDTO } from './dto/article-edit.dto';
import { IdDTO } from './dto/id.dto';
import { ListDTO } from './dto/list.dto';
import { ApiTags, ApiOkResponse } from '@nestjs/swagger';
import { ArticleInfoVO, ArticleInfoResponse } from './vo/article-info.vo';
import { ArticleListResponse, ArticleListVO } from './vo/article-list.vo';

@ApiTags('Article module')
@Controller('article')
export class ArticleController {
  constructor(
    private articleService: ArticleService
  ) {}

  @Get('list')
  @ApiOkResponse({ description: 'Article List'.type: ArticleListResponse })
  async getMore(
    @Query() listDTO: ListDTO,
  ): Promise<ArticleListVO> {
    return await this.articleService.getMore(listDTO)
  }

  @Get('info')
  @ApiOkResponse({ description: 'Article Details'.type: ArticleInfoResponse })
  async getOne(
    @Query() idDto: IdDTO
  ): Promise<ArticleInfoVO>{
    return await this.articleService.getOne(idDto)
  }

  @Post('create')
  @ApiOkResponse({ description: 'Create article'.type: ArticleInfoResponse })
  async create(
    @Body() articleCreateDTO: ArticleCreateDTO
  ): Promise<ArticleInfoVO> {
    return await this.articleService.create(articleCreateDTO)
  }

  @Post('edit')
  @ApiOkResponse({ description: 'Edit article'.type: ArticleInfoResponse })
  async update(
    @Body() articleEditDTO: ArticleEditDTO
  ): Promise<ArticleInfoVO> {
    return await this.articleService.update(articleEditDTO)
  }

  @Post('delete')
  @ApiOkResponse({ description: 'Delete article'.type: ArticleInfoResponse })
  async delete(
    @Body() idDto: IdDTO,
  ): Promise<ArticleInfoVO> {
    return await this.articleService.delete(idDto)
  }
}
Copy the code

At this point, we have introduced swagger automatic document generation in the project, as well as defined interface parameters and responses via DTO and Response types. In the future interface development, we can first define the type and then implement it. Moreover, thanks to swagger, after defining the type, interface consumers can easily mock data

In addition, Swagger provides return in JSON format. We can easily import swagger’s JSON format into other interface document tools, such as YAPI and Postman. Localhost :3000/swagger-doc-json: localhost:3000/swagger-doc-json

reference

  • NestJS
  • NestJS Chinese website
  • Code for this section

A series of

  • NestJS build blog system (a) – build a framework
  • NestJS build blog system (2) – write static article CURD
  • NestJS build blog system (3) – using TypeORM+Mysql to achieve data persistence
  • NestJS build blog system (four) – use interceptor, exception filter to achieve a unified return format
  • Use class-validator+ class validator to implement form validation