본문 바로가기
Research/Nest.js

nestjs_swagger 적용 및 dto OOP적 리팩토링

by RIEM 2023. 4. 5.
728x90

Swagger 모듈 설치

$ npm install --save @nestjs/swagger

swagger 모듈 설치(https://docs.nestjs.com/openapi/introduction)
과거에는 express, fastify로 구분된 것으로 보인다. 현재는 위의 명령어 한방이면 설치가 가능하다.

 

Swagger 생성 코드

 

// main.ts

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  app.useGlobalFilters(new HttpExceptionFilter());

  // Swagger 셋업
  const config = new DocumentBuilder()
    .setTitle('Swagger for Cats')
    .setDescription('The cats API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  // Swagger endpoint : ex) localhost:8000/docs
  SwaggerModule.setup('docs', app, document);

  const PORT = process.env.PORT;
  await app.listen(PORT);
}
bootstrap();

 

Swagger 확인

localhost:8000/docs 주소로 들어가면 확인할 수 있다. docs 엔드포인트는 위 setup 메소드의 인자로 지정할 수 있다.

nestjs가 컨트롤러를 참고하여 자동으로 만들어 준다.

 

Swagger API에 서머리 추가

...
@Controller('cats')
@UseInterceptors(SuccessInterceptor)
@UseFilters(HttpExceptionFilter)
export class CatsController {
  // catsService 종속성 주입
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getCurrentCat() {
    return 'current cat';
  }

  @ApiOperation({ summary: '회원가입을 위한 API' })
  @Post()
  async signUp(@Body() body: CatRequestDto) {
    return await this.catsService.signUp(body);
  }
...

서머리를 추가하고 싶은 API에 ApiOperation 데코레이터를 추가해주면 스웨거에 추가 메모를 남길 수 있다.

/cats POST의 원래 목적은 signUp인데 API 핸들러 이름으로만 그 기능을 파악하기가 어려운 문제를 서머리를 추가함으로서 해결했다. 다른 API 핸들러에도 각자 추가해주자.

 

Swagger POST API 에서 요청 JSON 샘플 정의하기 -> DTO

POST 요청의 경우 JSON에 어떤 프로퍼티를 채워야할지 알기 어렵다. 이 부분은 DTO를 활용하면 된다.

// src/cats/dto/cats.requrest.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';

export class CatRequestDto {
  // Swagger 샘플 데이터
  @ApiProperty({
    example: 'example@gmail.com',
    description: 'email',
    required: true,
  })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  // Swagger 샘플 데이터
  @ApiProperty({
    example: '1234!!',
    description: 'password',
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

  // Swagger 샘플 데이터
  @ApiProperty({
    example: 'john',
    description: 'name',
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  name: string;
}

DTO에 있는 엔티티에 ApiProperty 데커레이터를 추가하고, 인자로 샘플 데이터를 넣어주면 된다.

 

Swagger API Response 예상 응답 결과값 샘플 정의하기 -> Controller Hanlder

Response 부분에 내용이 없다. 보낼 데이터 예시를 시각화해주었으니, 응답 받을 데이터의 예시도 보여주자. Controller의 hanlder에 @ApiResponse() 데커레이터를 추가해주면 된다.

...
@Controller('cats')
@UseInterceptors(SuccessInterceptor)
@UseFilters(HttpExceptionFilter)
export class CatsController {
  // catsService 종속성 주입
  constructor(private readonly catsService: CatsService) {}

  @ApiOperation({ summary: '전체 고양이 데이터 조회' })
  @Get()
  getCurrentCat() {
    return 'current cat';
  }

  @ApiResponse({
    status: 500,
    description: 'There is something wrong in server',
  })
  @ApiResponse({
    status: 200,
    description: 'Request was successful',
    type: ReadOnlyDto,
  })
  @ApiOperation({ summary: '회원가입을 위한 API' })
  @Post()
  async signUp(@Body() body: CatRequestDto) {
    return await this.catsService.signUp(body);
  }
...

상태코드 500, 200에 대한 설명을 추가했다. 200의 경우, ReadOnlyDto을 연동해주어서 타입 체킹까지 해주고 있다.

 

Refactor-OOP: class 상속으로 재사용성 높이기

지금 dto의 엔티티를 하나하나 코딩하고 있다. class 상속 받아오는 방식으로 재사용성을 높여보자.

- 기존 방식 : DTO마다 스웨거 데코레이터 추가
- 새 방식 : Cat 스키마 클래스(스웨거 데코레이터 추가) -> DTO가 이를 상속하여 사용

// cats 폴더 내 cats.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { Document, SchemaOptions } from 'mongoose';

const options: SchemaOptions = {
  timestamps: true,
};

@Schema(options)
export class Cat extends Document {
  // 재사용상 향상을 위한 swagger 적용
  @ApiProperty({
    example: 'example@gmail.com',
    description: 'email',
    required: true,
  })
  @Prop({
    required: true,
    unique: true,
  })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @ApiProperty({
    example: 'john',
    description: 'name',
    required: true,
  })
  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  name: string;

  @ApiProperty({
    example: '1234!!',
    description: 'password',
    required: true,
  })
  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

  @Prop()
  @IsString()
  imgUrl: string;

  // 가상 엔티티 추가
  readonly readOnlyData: { id: string; email: string; name: string };
}

// Cat 클래스를 CatSchema 스키마로 생성
export const CatSchema = SchemaFactory.createForClass(Cat);

// virtual 메소드로 가상 엔티티 생성. 불필요한 password는 숨김처리하기 위함
CatSchema.virtual('readOnlyData').get(function (this: Cat) {
  // this라는 DB 객체에서 id, email, name을 추출
  return {
    id: this.id,
    email: this.email,
    name: this.name,
  };
});
// src/cats/dto/cats.requrest.dto.ts

import { PickType } from '@nestjs/swagger';
import { Cat } from '../cats.schema';

export class CatRequestDto extends PickType(Cat, [
  'email',
  'name',
  'password',
] as const) {
  // DTO 내용은 Cat 스키마에서 모두 상속
}
// cats.dto.ts
import { ApiProperty, PickType } from '@nestjs/swagger';
import { Cat } from '../cats.schema';

export class ReadOnlyDto extends PickType(Cat, ['email', 'name'] as const) {
  // 나머지는 Cat에서 상속
  // Cat에서 password 가져오지 않기 위해 PickType 사용

  // 몽구스에서 자동 생성하는 id를 위한 Swagger 샘플 데이터
  @ApiProperty({
    example: '1224124',
    description: 'email',
  })
  id: string;
}
728x90

댓글