ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NestJs Swagger 문서 만들기
    공부하기/node.js 2023. 3. 2. 22:02

    Swagger가 뭐야?

    API 문서화 툴로서, API의 요청/응답 데이터, 파라미터, 경로, 헤더 등 API를 이용하는 데 필요한 모든 정보를 제공합니다.

    또 API를 이용하는 클라이언트가 API를 쉽게 이해하고, 테스트할 수 있으며, 개발자는 API의 사용 방법을 명확하게 이해할 수 있습니다.

    저희는 NestJs에서 소개하는 @nestjs/swagger 모듈을 사용하여 스웨거 문서를 만들도록 하겠습니다.

     

    @nestjs/swagger를 사용하여 swagger 문서 만들기

    1. 모듈 install

    npm install --save @nestjs/swagger

    2. main.ts에서 swagger setup

    main.ts
    
    async function bootstrap() {
    ...
      const config = new DocumentBuilder()
        .setTitle('설문조사 스웨거')
        .setDescription('api 설명서')
        .setVersion('1.0')
        .build();
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('api', app, document);
    ...
    }
    bootstrap();

    3. 'http://localhost:[자신의_포트번호]/api/' 로 접속시 api와 스키마만 존재하는 비어있는 스웨거 문서가 자동으로 제작되어 있습니다.

     

     

    Types and parameters

    @ApiPropery 어노테이션을 사용하여 스웨거 schemas 프로퍼티 제작하기

    아래 예제 코드 결과

    export class SurveyCreateDto implements SurveyCreateInPortInputDto {
      @IsString()
      @ApiProperty({
        description: '설문지 이름',
        required: false,
      })
      name: string;
      @IsString()
      @ApiProperty({
        type: String,
        description: '설문지 상세설명',
        required: true,
        minimum: 0,
        maximum: 10,
        default: '디폴트!',
      })
      description: string;
    }
    • description: 프로퍼티를 설명하는 옵션입니다.
    • required: 필수값 여부, default는 true
    • minimum: 최소값을 명시합니다.
    • maximum: 최대값을 명시합니다.
    • default: default 값을 명시할 수 있습니다.

    @ApiQuery 어노테이션을 사용하여 스웨거 문서 api 파라메터 queryString 제작하기

    아래 예제 코드 결과

      @Get('/search')
      @ApiQuery({ name: 'page', required: false })
      @ApiQuery({ name: 'size', required: false })
      @ApiQuery({ name: 'sort', enum: SORT_OPTION, required: false })
      @ApiQuery({ name: 'keyword', required: false })
      async search(
        @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
        @Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number,
        @Query(
          'sort',
          new DefaultValuePipe(SORT_OPTION.ASC),
          new ParseEnumPipe(SORT_OPTION),
        )
        sort: SORT_OPTION,
        @Query('keyword', new DefaultValuePipe(''))
        keyword: string,
      ) {
    	... 
      }
    • 직접 명시하지 않아도 자동으로 매칭됩니다.
    • 저의 경우 쿼리 스트링의 디폴트값을 설정해뒀기 때문에 required를 변경하기 위해 사용했습니다.

    @ApiParam 어노테이션을 사용하여 스웨거 문서 api 파라메터 제작하기

    아래 예제 코드 결과

      @Get('find/:id')
      @ApiParam({ name: 'id', required: true })
      async findOne(@Param('id', ParseIntPipe) id: number) {
    	...
      }
    • 명시적으로 작성한 url 파라메터입니다.
    • 구지 작성하지 않아도 위 이미자와 동일한 결과를 볼 수 있습니다.

    Operations

    @ApiTags 어노테이션을 사용하여 스웨거 문서 컨트롤러 api 그룹화하기

    @Controller('survey')
    @ApiTags('survey')
    export class SurveyController extends ErrorController {
    	...
    }
    • @ApiTags('태그명')으로 컨트롤러에 속한 api를 그룹화합니다.

    @ApiOperation 어노테이션을 사용하여 스웨거 문서 엔드포인트 설명하기

    아래 예제 코드 결과

      @Get('find')
    	...
      @ApiOperation({
        summary: '설문지 리스트 요청',
        description: '설문지 리스트를 페이지 단위로 요청합니다.',
      })
    	...
      }
    • summary: 스웨거 문서의 엔드포인트를 펼치지 않아도 한눈에 확인할 수 있는 문구입니다.
    • description: 엔드포인트를 상세 설명합니다.

    @공통 응답 타입 만들기

    1. 스웨거 문서에서 사용할 엔티티 클래스에 @ApiProperty() 어노테이션 추가하기

    설문지 엔티티
    
    @Entity({
      name: 'survey',
    })
    export class Survey extends BaseEntity {
      @Index({ fulltext: true })
      @Column({ comment: '설문지 이름' })
      @ApiProperty()
      name: string;
    
      @Column({ type: 'text', comment: '설문지 설명' })
      @ApiProperty()
      description: string;
    
      @OneToMany((type) => Question, (question) => question.survey)
      @ApiProperty({ type: [Question] })
      question: Question[];
    }
    
    질문 엔티티
    
    @Entity({
      name: 'question',
    })
    export class Question extends BaseEntity {
      @Column({ type: 'tinyint', comment: '질문 번호' })
      @ApiProperty()
      question_number: number;
    
      @Column({ type: 'text', comment: '질문 내용' })
      @ApiProperty()
      text: string;
    
      @Column({ comment: '객관식/주관식' })
      @ApiProperty()
      type: string;
    
      @Column({ type: 'int' })
      @ApiProperty()
      survey_id: number;
    
      @ManyToOne((type) => Survey, (survey) => survey.question)
      @JoinColumn({ name: 'survey_id' })
      survey: Survey;
    
      @OneToMany((type) => Option, (option) => option.question)
      @ApiProperty({ type: [Option] })
      option: Option[];
    
      @OneToMany((type) => Response, (response) => response.question)
      @ApiProperty({ type: [Response] })
      responses: Response[];
    }
    
    
    
    선택지 엔티티
    
    @Entity({
      name: 'question_opt',
    })
    export class Option extends BaseEntity {
      @Column({ type: 'tinyint', comment: '보기 넘버' })
      @ApiProperty()
      option_number: number;
    
      @Column({ type: 'int' })
      @ApiProperty()
      question_id: number;
    
      @Column({ type: 'varchar', comment: '보기' })
      @ApiProperty()
      text: string;
    
      @ManyToOne((type) => Question, (opt) => opt.option)
      @JoinColumn({ name: 'question_id' })
      question: Question;
    }

     

    2. 공통 응답 타입 선언하기

    import { applyDecorators, Type } from '@nestjs/common';
    import {
      ApiOkResponse,
      ApiProperty,
      getSchemaPath
    } from '@nestjs/swagger';
    
    export class ResObj<T> {
      @ApiProperty()
      data: T;
      constructor(data: T) {
        this.data = data;
      }
    }
    
    export class ResObjList<T> {
      @ApiProperty()
      data: T[];
    
      @ApiProperty()
      count: number;
      constructor(data: T[], count: number) {
        this.data = data;
        this.count = count;
      }
    }
    
    export const ResOkObjList = <TModel extends Type<any>>(model: TModel) => {
      return applyDecorators(
        ApiOkResponse({
          schema: {
            allOf: [
              { $ref: getSchemaPath(ResObjList) },
              {
                properties: {
                  count: {
                    type: 'int',
                    default: 10,
                  },
                  data: {
                    type: 'array',
                    items: { $ref: getSchemaPath(model) },
                  },
                },
              },
            ],
          },
        }),
      );
    };
    
    export const ResOkObj = <TModel extends Type<any>>(model: TModel) => {
      return applyDecorators(
        ApiOkResponse({
          schema: {
            allOf: [
              { $ref: getSchemaPath(ResObj) },
              {
                properties: {
                  data: {
                    type: 'object',
                    properties: {
                      items: { $ref: getSchemaPath(model) },
                    },
                  },
                },
              },
            ],
          },
        }),
      );
    };

    3. 공통 응답 타입 스웨거 문서에 추가하기 (main.ts)

    async function bootstrap() {
    	...
      const config = new DocumentBuilder()
        .setTitle('설문조사 스웨거')
        .setDescription('api 설명서')
        .setVersion('1.0')
    
        .build();
      const options: SwaggerDocumentOptions = {
        extraModels: [ResObjList, ResObj],
      };
      const document = SwaggerModule.createDocument(app, config, options);
      SwaggerModule.setup('api', app, document);
    	...
    }
    bootstrap();

    4. 컨트롤러에서 사용할 엔티티 스웨거 문서에 추가하기

    @Controller('survey')
    @ApiTags('survey')
    @ApiExtraModels(Survey) <- 이거!!
    export class SurveyController extends ErrorController {
    	...
    }

    -> 엔티티는 기본적으로 스웨거가 자동으로 스웨거 스키마에 추가하지 않습니다.

    그러므로 컨트롤러에서 사용하는 Survey 엔티티 클래스 타입을 @ApiExtraModels 어노테이션을 사용해서 스웨거 문서에 추가합니다.

     

    5. 공통 응답 타입 엔드포인트에 적용하기

      @Get('find')
      ...
      @ResOkObjList(Survey)
      ...
      async findAll(
    	...
      ) {
    	...
      }
    
      @Get('find/:id')
      ...
      @ResOkObj(Survey)
      async findOne(@Param('id', ParseIntPipe) id: number) {
    	...
      }

     

    결과

    ResObjList
    ResObj

     

    엔드포인트 에러 리스폰스 스웨거 문서에 추가해보기

      @Get('find')
      ...
      @ApiNotFoundResponse({ description: 'Not Found Error' }) <- 이거!
      ...
      async findAll(
        ...
        )
        sort: SORT_OPTION,
      ) {
    	...
      }
    • 404 에러 응답 스웨거 문서에 추가

    결과

     

     

     

     

     

     

     

     

    이렇게 기본적으로 스웨거 문서 제작에 필요한 어노테이션을 알아봤습니다.

    '공부하기 > node.js' 카테고리의 다른 글

    NestJs, Redis 캐싱 기록  (0) 2023.03.12
    NestJs Cache 탐색 및 기록  (0) 2023.03.09
    헥사고날 NestJs, TypeOrm dto type 리펙토링  (0) 2023.02.26
    NestJs Controller @Query Pipe 기록  (0) 2023.02.25
    NestJs TypeOrm Exception  (0) 2023.02.24
Designed by Tistory.