티스토리 뷰

 

Nest에서 스웨거를 사용하는 방법을 설명합니다.

 


API 문서화

API 문서화란 API 사용 방법을 문서로 정리하는 것을 말합니다.

 

보통 엔드포인트와 HTTP 메서드, 요청 및 응답 형식, 인증 방법, 예시 등이 내용으로 들어갑니다.

API 문서는 워드나 노션 이용해 개발자가 직접 수기로 작성할 수도 있고, 스웨거와 같은 도구를 이용할 수도 있습니다.

 

쉽게 말해 `'API 요청 주소는 여기고요. 요청 파라미터는 이런 것들이 있습니다. 이렇게 호출하면 이런 결과가 나와요~! ✋'`

와 같이 API 사용 설명서를 만드는 일이라고 할 수 있습니다.

 

공공데이터포털에서 예시를 쉽게 찾아볼 수 있습니다. 

 

 

 

왜 필요한가요?

API 사용자의 편의를 위해서 입니다!

사용자는 보통 프론트엔드 개발자나, 해당 API를 이용해 서비스를 구현하려는 다른 개발자가 되겠죠.

 

API 문서는 API가 제공하는 기능과 기능의 동작 방식에 대해 더 명확하게 설명하고, 불필요한 소통을 줄여줍니다.

또한 올바른 사용법을 안내하여 예상치 못한 오류를 방지하도록 합니다.

 

즉, API 문서화는 개발자들이 API를 쉽게 이해하고 효율적으로 활용할 수 있도록 도와줍니다. 💡

 

 


Swagger

스웨거(Swagger)는 널리 사용되는 API 문서 자동화 도구입니다. 

 

 

API Documentation & Design Tools for Teams | Swagger

Loved by all • Big & Small Thousands of teams worldwide trust Swagger to deliver better products, faster.

swagger.io

 

스웨거를 사용하면 별도의 문서를 작성하지 않아도 코드 내에서 API 문서를 만들고 수정할 수 있습니다.

 

코드와 문서를 따로 관리하게 되면, 둘 중 하나만 수정된 경우 실제 API와 문서상 API의 동작이 달라지는 문제가 발생하겠죠?

스웨거는 여기서 발생하는 불편함을 줄여줍니다.

 

다른 장점으로는 Swagger UI를 통해 작성된 API를 시각함과 동시에, 테스트할 수 있는 화면을 제공한다는 것입니다.

 

 

NestJS에서 Swagger 사용하기

 

설치

간단히 사용할 수 있는 NestJS 용 패키지가 존재합니다. 아래 명령어로 패키지를 설치합니다. 🎉

npm install --save @nestjs/swagger

 

 

swagger.config.ts 작성

이어서 config 파일을 작성합니다. main.ts에 바로 작성해도 되지만, 분리해 두는 것이 가독성에 좋으니까요. 

import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

export function setupSwagger(app: INestApplication): void {
  const options = new DocumentBuilder()
    .setTitle('Test API Docs') // API 명
    .setDescription('NestJS Swagger Test') // API 설명
    .setVersion('1.0.0') // 버전
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document); // API 문서 주소
}

 

 

그리고 main.ts에 setupSwagger()를 추가합니다.

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

 

 

 

서버를 작동시키고, http://localhost:3000/api로 접속하면 아래와 같은 화면을 볼 수 있습니다.

 

 

swagger를 사용할 준비는 다 되었습니다. 이제 실제로 문서화를 진행해 볼까요?

 

 

컨트롤러에 API 명세 추가하기

회원가입을 진행하는 auth모듈의 컨트롤러 코드 일부를 가져왔습니다.

 

@nestjs/swagger에서 제공하는 데코레이터들을 이용해 API 정보를 추가해 보겠습니다.

(이 글에서는 간단한 몇 개의 데코레이터들만 소개합니다!)

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @HttpCode(201)
  @Post('/signup')
  async buyerSignUp(@Body() createUserDto: CreateBuyerDto): Promise<void> {
    await this.authService.buyerSignUp(createUserDto);
  }

...

}

 

 

@ApiTags
컨트롤러 클래스에 태그를 지정하는 데 사용됩니다. 각 태그는 스웨거 UI에서 API 문서를 구성할 때 사용됩니다.

 

@ApiOperation()
컨트롤러의 메서드에 대한 작업(operation)에 대한 설명을 추가하는 데 사용됩니다. 
summary 속성에는 간결한 제목을 작성하고, description에 상세 설명을 작성합니다.

 

@Controller('auth')
@ApiTags('Auth API')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @HttpCode(201)
  @Post('/signup')
  @ApiOperation({ summary: 'buyer 생성 API', description: 'buyer를 생성한다.' })
  async buyerSignUp(@Body() createUserDto: CreateBuyerDto): Promise<void> {
    await this.authService.buyerSignUp(createUserDto);
  }

...

}

 

 

이제 Swagger UI에서 태그와 설명을 확인할 수 있습니다.

 

 

 

DTO에 API 명세 추가하기

지금은 Request body가 비어있는데요. 이곳에 요청 DTO에 대한 정보를 추가해 보겠습니다. 

 

제 프로젝트에서 CreateBuyerDto는 AuthCredentialsDto를 상속받고 있습니다.

따라서 두 클래스에 각각 추가해보겠습니다. 

 

 

- AuthCredentialsDto

import { IsEmail, Matches } from 'class-validator';
import { IsNotEmptyString } from '../../decorators/is-not-empty-string.decorator';

export class AuthCredentialsDto {
  @IsEmail()
  email!: string;

  @IsNotEmptyString(6, 20)
  @Matches(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*?&.])[A-Za-z\d@$!%*?&.]{6,}$/, {
    message: '비밀번호는 문자, 숫자, 특수문자(@, $, !, %, *, ?, &, .)의 조합으로 6자 이상 20자 이하로 입력해주세요 ',
  })
  password!: string;
}

 

 

- CreateBuyerDto

import { Transform } from 'class-transformer';
import { IsNotEmptyNumber } from '../../decorators/is-not-empty-number.decorator';
import { IsNotEmptyString } from '../../decorators/is-not-empty-string.decorator';
import { BuyerEntity } from '../buyer.entity';
import { AuthCredentialsDto } from './auth-credentials.dto';

export class CreateBuyerDto
  extends AuthCredentialsDto
  implements Pick<BuyerEntity, 'name' | 'gender' | 'age' | 'phone'>
{
  @IsNotEmptyString(1, 128)
  name!: string;

  @IsNotEmptyNumber()
  gender!: number;

  @IsNotEmptyNumber()
  age!: number;

  @Transform(({ value }) => value.replace(/-/g, ''))
  @IsNotEmptyString(11, 11)
  phone!: string;
}

 

 

@ApiProperty()
클래스의 속성(property)에 대한 설명을 추가하는 데 사용됩니다.  주요 속성은 다음과 같습니다.

  • type: 속성의 데이터 유형
  • description: 속성의 설명
  • required: 속성의 필수 여부
  • example: 속성의 예시 값
  • minLengthmaxLength: 문자열 속성의 최소 및 최대 길이
  • minimummaximum: 숫자 속성의 최소 및 최댓값
  • enum: 이넘 타입의 경우, 지정가능한 속성 목록

 

저는 이 중에 type, description, required, example를 추가해 보았습니다.

 

 

- AuthCredentialsDto

export class AuthCredentialsDto {
  @ApiProperty({ type: String, description: '이메일', required: true, example: 'myemail@gmail.com' })
  @IsEmail()
  email!: string;

  @ApiProperty({
    type: String,
    description: '비밀번호 (문자, 숫자, 특수문자(@, $, !, %, *, ?, &, .)의 조합으로 6자 이상 20자 이하로 입력해주세요)',
    required: true,
    example: 'mypassword1!',
  })
  @IsNotEmptyString(6, 20)
  @Matches(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*?&.])[A-Za-z\d@$!%*?&.]{6,}$/, {
    message: '비밀번호는 문자, 숫자, 특수문자(@, $, !, %, *, ?, &, .)의 조합으로 6자 이상 20자 이하로 입력해주세요 ',
  })
  password!: string;
}

 

 

CreateBuyerDto

export class CreateBuyerDto
  extends AuthCredentialsDto
  implements Pick<BuyerEntity, 'name' | 'gender' | 'age' | 'phone'>
{
  @ApiProperty({ type: String, description: '이름', required: true, example: 'myname' })
  @IsNotEmptyString(1, 128)
  name!: string;

  @ApiProperty({ type: Number, description: '성별(남자 0, 여자 1)', required: true, example: 1 })
  @IsNotEmptyNumber()
  gender!: number;

  @ApiProperty({ type: Number, description: '나이', required: true, example: 20 })
  @IsNotEmptyNumber()
  age!: number;

  @ApiProperty({
    type: String,
    description: '휴대전화번호 ( - 는 포함하지 않습니다 )',
    required: true,
    example: '01012341234',
  })
  @Transform(({ value }) => value.replace(/-/g, ''))
  @IsNotEmptyString(11, 11)
  phone!: string;
}

 

 

이제 Request body에서 DTO에 대한 예시 및 정보를 확인할 수 있습니다.

 

 

 

 

하단의 스키마 영역에서는 DTO에 대한 자세한 정보를 확인할 수 있습니다.

 

 

 

 

테스트해보기

Try it out 버튼을 사용해 스웨거 상에서 API 요청을 날려볼 수 있습니다.

제 서버에는 이미 `myemail@gmail.com` 계정이 존재하기 때문에 에러가 응답으로 날아와야 합니다. 

 

 

 

 

예상대로 에러가 응답값으로 반환되었습니다.

 

 

 


 

JWT 테스트하기

guard를 붙여놓은 컨트롤러도 스웨거에서 테스트할 수 있을까요?  JWT는 어떻게 받아오죠? 🤔

 

@Controller('seller')
@ApiTags('Seller API')
@UseGuards(SellerJwtAuthGuard) // seller 로그인 후 접속가능
export class SellerController { ... }

 

 

@ApiBearerAuth
보통 JWT 인증에 사용됩니다.

해당 엔드포인트에 대한 요청이 인증되지 않았거나, 유효하지 않은 경우에 엔드포인트에 접근할 수 없도록 합니다.
API 서버는 클라이언트의 요청 헤더에 포함된 JWT를 확인하여 권한을 부여합니다.

 

 

먼저 해당 엔드 포인트에 ApiBearerAuth를 추가합니다. 저는 'token'이라는 이름을 부여했습니다.

@Controller('seller')
@ApiBearerAuth('token')
@ApiTags('Seller API')
@UseGuards(SellerJwtAuthGuard) // seller 로그인 후 접속가능
export class SellerController { ... }

 

 

 

ApiBearerAuth이 설정된 엔드포인트들은 아래와 같이 자물쇠 모양이 나타납니다. 

 

 

 

 

 

이어서 swagger.config.ts를 수정해야 합니다. 앞서 사용한 코드는 아래와 같습니다. 

이곳에 인증 옵션을 넣어주면 됩니다.

import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

export function setupSwagger(app: INestApplication): void {
  const options = new DocumentBuilder()
    .setTitle('Test API Docs') // API 명
    .setDescription('NestJS Swagger Test') // API 설명
    .setVersion('1.0.0') // 버전
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document); // API 문서 주소
}

 

 

bearer 인증에 대한 내용은 `addBearerAuth` 메서드를 이용하면 됩니다.

 addBearerAuth(options?: SecuritySchemeObject, name?: string): DocumentBuilder;

 

 

SecuritySchemeObject은 아래와 같네요.

export type SecuritySchemeType = 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
export interface SecuritySchemeObject {
    type: SecuritySchemeType;
    description?: string;
    name?: string;
    in?: string;
    scheme?: string;
    bearerFormat?: string;
    flows?: OAuthFlowsObject;
    openIdConnectUrl?: string;
    'x-tokenName'?: string;
}

 

 

저는 JWT를 이용할 것이기 때문에 아래와 같이 작성했습니다.

위와 동일하게 'token'이라는 이름을 부여했습니다. 다르면 작동하지 않으니 주의하셔야 합니다.

import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

export function setupSwagger(app: INestApplication): void {
  const options = new DocumentBuilder()
    .setTitle('Test API Docs') // API 명
    .setDescription('NestJS Swagger Test') // API 설명
    .setVersion('1.0.0') // 버전
    .addBearerAuth(
      {
        type: 'http',
        scheme: 'bearer',
        name: 'JWT',
        description: 'Enter JWT token',
        in: 'header',
      },
      'token',
    )
    .build()
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('api', app, document); // API 문서 주소
}

 

 

 

Swagger UI 상단에 Authorize 아이콘이 추가된 것을 확인할 수 있습니다.

 

 

 

 

해당 버튼을 누르면 아래와 같은 화면이 나옵니다. 

 

 

 

곳에 발급된 토큰을 넣어주시면 됩니다.

 

 

 

입력한 JWT는 테스트 요청 시 헤더에 담겨 함께 전송된 후 서버 내부 로직으로 처리되게 됩니다. ✌️

 


 

 

스웨거를 사용하다보면 아시겠지만, 결국 비즈니스 로직 코드 외에 다른 코드들을 추가는 것이다 보니

문서화를 자세히 할수록 코드가 복잡해진다는 단점이 발생합니다.

그럼에도 저에겐 깔끔한 UI와 테스팅의 장점이 더 크게 느껴지는 것 같습니다.  🤔

 

 

감사합니다.

 

 

 

[참고자료]

 

 

https://jhyeok.com/nestjs-swagger/

이 글은 NestJS에서 Swagger를 사용하면서 정리한 글입니다. 이 글에서 사용된 코드는 여기에서 확인할 수 있습니다. Swagger란? Swagger는 API 문서 자동화 도구이다. 이전의 프로젝트에서 사용한 경험으

jhyeok.com

댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday