[Nest] Swagger API 적용

주형(Jureamer)·2022년 4월 9일
0

서론

2번의 프로젝트 동안 Gitbook을 사용해서 API 명세들을 관리했다. 처음부터 Gitbook을 써와서 다른 걸 써볼 생각은 안했는데 프로젝트를 진행하면서 느낀 불편한 점들이 있어 대체재들이 있는 지 찾아봤다. 그러다가 Swagger API가 자동화와 테스트를 지원해서 사용 해 보기로 했다.

내가 느낀 Gitbook만 사용했을 때의 단점은 다음과 같다.

Gibook 단점

1. API 명세 내용이 변경 될 때마다 웹페이지에서 하나하나 변경 해줘야한다.

큰 단점이라고 생각한다. Gitbook은 거의 API 명세를 관리하는 메모장의 기능이기에, 설계단계가 끝나고 작성할 때는 참 간편하지만 프로젝트 중반에 API가 변경된다고 귀찮은 일이 아닐 수 없다.

물론 변경이 일어나지 않도록 설계를 잘 ~ 짜는 게 중요하지만 그럼에도 불구하고 다양한 이유로 인해 API는 언제든지 변경 될 수 있다.

2. 무료 버전은 작성자만 변경 가능하다.**

우리는 프로젝트 팀장님이 작성하다보니 나중에 변경사항이 생길 때마다.
각 API담당자가 노션에 정리하고 그걸 팀장님이 다시 수정하는.. 일이 반복되었다.

아마 기업에서 사용하게 된다면 유료 버전을 사용할 확률이 높을테니 이건 토이프로젝트 같은 작은 프로젝트에서의 단점일 것이다.

Swagger API 장점

그렇담 Swagger의 장점은 뭘까? Gitbook의 단점을 정확히 반대로보완할 수 있다.

1. (반)자동화가 가능하다.

완전 자동화는 아닌 것이 당연히 어떤 코드인 지, 타입인 지 명세들을 일일이 추가해줘야하는 것은 동일하다. 다만 코드를 작성하며 그 부분은 자연스럽게 추가할 수 있어 웹페이지로 이동해서 작성하는 것과는 비교가 불가능할 정도로 편리하다.

2. API 테스트가 가능하다.

기존에는 Postman을 통해서 API 테스트를 해야했다. 하지만 Swagger API는 (잘) 작성만 된다면 문서 내에서 쉽고 간편하게 테스트가 가능하다.

이러한 장점을 가진 Swagger API를 어떻게하면 NestJS에 적용할 수 있을 지 알아보도록 하자.

NestJS에 Swagger 적용하기

1. 모듈 다운

우선 nest에서 사용하기 위해 npm을 통해 모듈들을 다운 받아야 한다.

// 일반적인 경우
npm install --save @nestjs/swagger swagger-ui express

//fastify를 사용하는 경우
npm install --save @nestjs/swagger fastify-swagger

2. 환경설정

그 다음 Main.ts에서 아래와 같이 환경 설정을 하자.

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: true,
  });
  
// 여기부터 Swagger 설정 시작
  const config = new DocumentBuilder()
  .setTitle('Roadgram')
  .setDescription('Roadgram API Document')
  .setVersion('1.0')
  .addTag('roadgram')
  .build();

  const document = SwaggerModule.createDocument(app, config);
  // 첫 번째 변수는 url/변수의 형태로 URL 경로를 설정할 수 있다.
  // ex) localhost:5000/api
  SwaggerModule.setup('api', app, document, {
    // swagger option 중 하나로, 기본적으로 보이는 Dto를 숨기는 option
    swaggerOptions: {defaultModelsExpandDepth: -1}
  });
  
  // Swagger 설정 끝

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );

  app.use(cookieParser());
  await app.listen(process.env.SERVER_PORT);
}
bootstrap();

설정을 하고 서버를 킨 뒤 URL(나 같은 경우localhost:5000/api)에 접속하면 아래와 같이 문서가 짜잔하고 뜬다.

3. API 문서화

이제 데코레이터를 통해 문서화를 해보자
데코레이터에 대한 정보는 공식문서 를 통해 확인할 수 있다.

- 태그 설정

ApiTags를 통해 태그를 줘 분류를 할 수 있다.
각 메소드마다 각각 지정을 가능하고, Controller 전체를 동일한 태그로 묶고싶다면 최상단에 선언해주면 된다.

@Controller('articles')
@ApiTags('Articles')
export class ArticlesController {
  constructor(
    private articlesService: ArticlesService,
  ) {}

 ....

- API 설명 추가

ApiOpreation에 summary, description 옵션을 통해 설명을 추가할 수 있다.

  @Get()
  @ApiOperation({summary: "메인 페이지 팔로우 조회", description: "메인 페이지에서 팔로우한 사람의 게시물들을 조회한다."})
 ...

- 입력 값 명세 추가

DTO를 사용한다면 DTO에 각 변수마다 ApiProperty를 지정하여
명세를 입력할 수 있다.

export class CreateArticleDto {
  @ApiProperty({description: '유저ID'})
  @IsNotEmpty()
  @IsNumber()
  user: number;

그 외 나는 사용하지 않았지만 ApiQuery, ApiParam 등을 사용하여 Controller 부분에서 추가 해 줄수도 있다.

- 응답 추가

ApiOkResponse, ApiNotFoundResponse 등을 통해 각 응답코드에 해당하는 데코레이션을 추가할 수 있다.

@Get()
  @ApiOperation({summary: "메인 페이지 팔로우 조회", description: "메인 페이지에서 팔로우한 사람의 게시물들을 조회한다."})
  @ApiOkResponse({description: '팔로우 게시물을 정상적으로 조회함', type: GetMainResponse})
  @ApiUnauthorizedResponse({description: '권한이 없음', type: GetMain401Response})
  @ApiNotFoundResponse({description: '팔로잉 유저를 찾을 수 없음', type: GetMain404Response})

type에 해당하는 response는 별도의 추상 클래스들로 만들어주었다.

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

export abstract class ArticleObject {
  @ApiProperty({example: 5})
  id: number
  
  @ApiProperty({example: 3})
  userId: number

  @ApiProperty({example: 'thumbnail.jpg'})
  thumbnail: string

  @ApiProperty({example: "테스트 계정"})
  nickname: string

  @ApiProperty({example: "profile.jpg"})
  profileImage: string
  
  @ApiProperty({example: 3})
  totalLike: number

  @ApiProperty({example: 5})
  totalComment: number
  
  @ApiProperty({example: {order: 1, tagName: "테스트"}})
  tags: Object[] | []
};

export abstract class ArticlesData {
  @ApiProperty()
  articles: ArticleObject
};

export abstract class GetMainResponse {
  @ApiProperty()
  data: ArticlesData
  
  @ApiProperty({example: "ok"})
  message: string
}

export abstract class GetMain401Response {
  @ApiProperty({example: 'permisson denied'})
  message: string
}

export abstract class GetMain404Response {
  @ApiProperty({example: 'not found following ids'})
  message: string
}

완성물!

나는 이미 코드가 다 짜여진 프로젝트에서 Swagger를 적용시켰다.

다음부터는 설계 단계부터 적용 시켜 볼 예정인데
코드나 변수까지 설정하며 적용하긴 쉽지 않을 것 같다. 아마 중간중간 바꿔줘야할 듯하다.
그럼에도 불구하고 Gitbook에 비해선 편리한 건 사실.

실무에서 Swagger를 사용하는 곳은 Swagger로만 API 명세를 관리하는 지, Gitbook 같은 툴을 병행해서 사용하는 지 궁금하긴 하다.

끗.

Reference

profile
작게라도 꾸준히 성장하는게 목표입니다.

0개의 댓글