NestJS 帶你飛! 시리즈 번역 27# Swagger(하)

2023. 7. 1. 23:00개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.

 

API 설계

위의 포스팅에선 API 파라미터가 Swagger UI에 표시되도록 하는 방법을 배웠습니다.

파라미터를 설계한 후에는 API를 분류할 수 있으며 요청/응답과 관련된 내용들도 Swagger UI에 포함시킬 수 있습니다.

 

Tags

이 기능은 API를 분류하는데 쓰입니다.
@ApiTags 데코레이터를 사용해 특정 컨트롤러에 태그를 지정하여 Swagger UI에서 해당 API를 쉽게 찾을 수 있습니다.

 

TodoController를 수정하여 @ApiTags를 적용한 후 태그를 Todo라고 명명하겠습니다.

 

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiBody, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// 태그 추가
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

 

http://127.0.0.1:3000/api 에 접속하면 Todo 영역이 생긴것을 확인할 수 있습니다.

 

 

Headers

이 기능은 Swagger UI에서 API에 대한 인터페이스를 제공하여 특정 Header의 내용을 구성할 수 있게 도와줍니다.

@ApiHeader 데코레이터를 통해 name 속성을 지정해주면 UI에서 해당 필드를 볼 수 있게됩니다.

 

TodoController의 내용을 수정하여 getTodo@ApiHeader 데코레이터를 적용하고 name X-Custom으로 지정하겠습니다.

 

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  // Swagger UI에 X-Custom 필드를 채울 수 있게 해줍니다.
  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

http://127.0.0.1:3000/api에 접속하면 아래와 같은 결과를 확인할 수 있습니다.

 

 

Responses

이 기능은 Swagger UI에서 API가 반환하는 각각의 HttpCode의 의미를 표시할 수 있습니다.

@ApiResponse 데코레이터를 사용하여 statusdescription을 지정하면 됩니다.

 

TodoController를 수정해 createTodo@ApiResponse 데코레이터를 적용 하고 statusHttpStatus.CREATED 로 지정하겠습니다.

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  // Swagger UI에서 201 상태를 볼 수 있습니다.
  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

 

브라우저에서 http://127.0.0.1:3000/api에 접속해보면 201 코드에 대한 설명이 The todo has been successfully created.

라고 적혀있는걸 확인할 수 있습니다.

 

Nest에서는 각종 상태들이 각각 독립적으로 패키징되어 있으며 아래에 몇가지를 소개하겠습니다.

관심이 있다면 아래의 데코레이터들이 무엇인지 혼자 실험해보아도 좋습니다!

  • @ApiOkResponse()
  • @ApiCreatedResponse()
  • @ApiAcceptedResponse()
  • @ApiNoContentResponse()
  • @ApiMovedPermanentlyResponse()
  • @ApiFoundResponse()
  • @ApiBadRequestResponse()
  • @ApiUnauthorizedResponse()
  • @ApiNotFoundResponse()
  • @ApiForbiddenResponse()
  • @ApiMethodNotAllowedResponse()
  • @ApiNotAcceptableResponse()
  • @ApiRequestTimeoutResponse()
  • @ApiConflictResponse()
  • @ApiPreconditionFailedResponse()
  • @ApiTooManyRequestsResponse()
  • @ApiGoneResponse()
  • @ApiPayloadTooLargeResponse()
  • @ApiUnsupportedMediaTypeResponse()
  • @ApiUnprocessableEntityResponse()
  • @ApiInternalServerErrorResponse()
  • @ApiNotImplementedResponse()
  • @ApiBadGatewayResponse()
  • @ApiServiceUnavailableResponse()
  • @ApiGatewayTimeoutResponse()
  • @ApiDefaultResponse()

 

API 권한 설계

API에 권한을 통해야만 사용하고 싶게 하고싶다면 Swagger에서는 어떻게 설정해야 할까요?

개발자가 Swagger UI에서 인증 정보를 구성해 API에 엑세스할 수 있도록 지정된 데코레이터를 사용해
구현할 수 있습니다. 

아래에서는 3가지 인증 방법을 설명하겠습니다. 

 

Basic

Basic 인증 방법으로 인증을 진행하려면 @ApiBasicAuth 데코레이터를 적용시키고자 하는 API에 적용시키면 되는데

이때 DocumentBuiler 인스턴스의 addBasicAuth 메서드로 인증 필드를 활성화 시켜주어야 합니다.

 

TodoController를 수정하여 TodoController@ApiBasicAuth 데코레이터를 추가하겠습니다.

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBasicAuth, ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// Basic Auth
@ApiBasicAuth()
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

 

 

이어서 main.ts를 수정하겠습니다. builderaddBasicAuth 메서드를 호출하도록 하겠습니다.

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addBasicAuth() // 권한 인증 필드 활성화
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

http://127.0.0.1:3000/api에 접속하면 Todo 영역에 API들에 전부 자물쇠가 채워진걸 확인할 수 있습니다.

 

이는 권한 인증에 성공해야만 호출할 수 있음을 의미하며 Authorize 버튼을 눌러 인증을 진행한 후 권한을 획득할 수 있습니다.

 

Bearer

Bearer 인증 방법을 사용하려면 @ApiBearerAuth 데코레이터로 적용하고자 할 API 위에 추가해주면 됩니다.

이 인증방법 또한 DocumentBuiler 인스턴스의 addBasicAuth 메서드로 인증 필드를 활성화 시켜주어야 합니다.

 

TodoController를 수정해 @ApiBearerAuth 데코레이터를 추가해주겠습니다.

 

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiHeader, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// Bearer Auth
@ApiBearerAuth()
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

 

이어서 main.ts를 수정하여 builderaddBearerAuth 메서드를 호출해주겠습니다.

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addBearerAuth() // 인증 필드 활성화
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

 

http://127.0.0.1:3000/api에 접속해보면
Basic과 일치하는것을 확인할 수 있지만 인증 방식이 다르다는점을 명심해야합니다.

 

OAuth2

OAuth 인증방법을 사용하려면 @ApiOAuth2 데코레이터를 적용하고자 하는 API 위에 작성해주면 됩니다.

DocumentBuilder 인스턴스 중에 addOAuth2 메서드를 통해 인증 필드를 활성화 시켜주어야 합니다.

하지만 여기서는 typeflow 메서드를 설정해주어야만 해당 인증방법이 완성됩니다.

 

TodoController를 수정하여 TodoController@ApiOAuth2 데코레이터를 적용시켜보겠습니다.

import { Body, Controller, Get, HttpStatus, Param, Post } from '@nestjs/common';
import { ApiBody, ApiHeader, ApiOAuth2, ApiResponse, ApiTags } from '@nestjs/swagger';

import { TodoService } from './todo.service';

import { CreateTodoDto } from './dto/create-todo.dto';

// OAuth2 Auth
@ApiOAuth2(['write', 'read', 'update'])
@ApiTags('Todo')
@Controller('todos')
export class TodoController {
  constructor(private readonly todoService: TodoService) {}

  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'The todo has been successfully created.',
  })
  @Post()
  createTodo(@Body() data: CreateTodoDto) {
    return this.todoService.createTodo(data);
  }

  @ApiBody({ type: [CreateTodoDto] })
  @Post('bulk')
  createTodos(@Body() todos: CreateTodoDto[]) {
    return todos.map((todo) => this.todoService.createTodo(todo));
  }

  @ApiHeader({
    name: 'X-Custom',
    description: 'Try to set custom header.',
  })
  @Get(':id')
  getTodo(@Param('id') id: string) {
    return this.todoService.getTodo(id);
  }
}

 

이어서 main.ts를 수정하겠습니다. builderaddOAuth 메서드를 호출하고 typeoauthflowimplicit 파라미터에 authorizationUrltokenUrl, scopes를 설정해줍니다.

import { NestFactory } from '@nestjs/core';
import { INestApplication } from '@nestjs/common';
import {
  DocumentBuilder,
  SwaggerCustomOptions,
  SwaggerModule,
} from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  setupSwagger(app);
  await app.listen(3000);
}

function setupSwagger(app: INestApplication) {
  const builder = new DocumentBuilder();
  const config = builder
    .setTitle('TodoList')
    .setDescription('This is a basic Swagger document.')
    .setVersion('1.0')
    .addOAuth2({
      type: 'oauth2',
      flows: {
        implicit: {
          authorizationUrl: '<AUTHORIZATION_URL>', // 인증 URL
          tokenUrl: '<TOKEN_URL>', // 인증용  토큰
          scopes: {
            // 권한 범위
            read: 'read',
            write: 'write',
            update: 'update',
            delete: 'delete',
          },
        },
      },
    })
    .build();
  const document = SwaggerModule.createDocument(app, config);
  const options: SwaggerCustomOptions = {
    explorer: true,
  };
  SwaggerModule.setup('api', app, document, options);
}

bootstrap();

 

http://127.0.0.1:3000/api에 접속하면 Basic과 같은 인증 효과를 얻을 수 있지만
인증 정보를 구성하는 부분에서 차이가 있습니다.
이 인증방식에선 Swagger는 해당 서비스에 대해 인증요청을 진행 것입니다.


마치며

Swagger UI를 디자인하는것은 전체 개발인원 효율에 영향을 미칩니다.

앞서 배운 파라미터 설계, 이번에 소개하는 작업과 인증 설계를 잘만 활용한다면
프론트엔드와 백엔드 간의 커뮤니케이션 비용을 크게 줄일 수 있을것입니다.

Swagger UI는 학습할만한 가치가 있는 도구이니 개발자라면 반드시 익숙해져야 할 것입니다.

 

아래는 오늘 포스팅의 요약본입니다.

 

1. @ApiTags를 사용해 API를 분류할 수 있습니다.

2. @ApiHeader를 사용해 지정된 Header 필드를 활성화할 수 있습니다.

3. @ApiResponses를 사용해 Swagger UI에 API 반환값 각각의 HttpCode가 어떤 의미인지를 표시할 수 있습니다.

4. Nest는 각각의 상태를 독립적으로 래핑하였습니다. (ex: @ApiOkResponse)

5. 권한을 설정한다면 @ApiBasicAuth, @ApiBearerAuth@ApiOAuth2를 사용할 수 있습니다.

6. 권한 설정은 DocumentBuilder 인스턴스의 해당하는 인증방법과 대응되는 메서드를 호출해 인증 필드를 활성화 시켜야 합니다.