-
Notifications
You must be signed in to change notification settings - Fork 2
Swagger를 적용해보자
개발한 Rest API를 편리하게 문서화 해주고, 사용자가 편리하게 API를 호출해보고 테스트까지 할 수 있는 도구입니다.
- API문서를 수동으로 작성/관리할 필요가 없다. (자동화)
- 그렇기에 코드와 문서의 최신성을 보장한다.
- API 테스트를 위한 별도 도구 (Postman 등) 없이 바로 테스트가 가능하다.
사실 API 문서를 자동화하고, 코드 내부에서 문서를 관리할 수 있다는게 가장 큰 장점 같아요.
pnpm add --save @nestjs/swagger
//swagger.config.ts
export class ConfigSwagger {
static setup(app: INestApplication<any>) {
const config = new DocumentBuilder()
.setTitle("Cam'ON API 문서")
.setDescription("Cam'ON 서비스의 api 문서입니다.")
.setVersion('1.0')
.addTag(SwaggerTag.HEADER)
.addTag(SwaggerTag.MAIN)
.addTag(SwaggerTag.WATCH)
.addTag(SwaggerTag.BROADCAST)
.addTag(SwaggerTag.MYPAGE)
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('camon-api-docs', app, document);
}
}
- ConfigSwagger class를 작성하여 Config를 설정해주었습니다.
- DocumentBuilder 함수는 문서에 대한 config를 설정하는 함수입니다.
- 앞에서 문서에 대한 설정을 한 config로 swaggerModule의 createDocument함수를 호출하여 문서를 생성합니다.
- 생성된 문서를 SwaggerModule의 setup함수의 파라미터로 넘깁니다.
- 명세서에서 커스텀 기준에 맞게 Api분리를 하기 위해서 addTag를 추가하여 Tag도 추가해주었습니다.
세팅 적용 결과
- 헤더, 메인페이지, 방송 시청 화면, 방송 송출 화면, 마이페이지는 제가 직접 적용한 tag이고 Broadcast는 이미 코드에 구현된 Controller가 자동으로 반영된 것입니다.
- 자동으로 Controller가 반영되는것을 최대한 끄고 싶었는데 아무리 찾아봐도 방법이 보이지 않더라구요
ConfigSwagger.setup();
- 작성한 swagger config class는 main.ts에서 위와 같이 사용해줍니다.
Spring에서 Swagger을 사용할때와, Nest에서 Swagger를 사용하는 방법이 매우 달랐습니다.
Spring은 거의 자동화에 최적화되어 별다른 코드를 많이 작성해 주지 않아도 api문서가 저절로 생기는데, Nest같은 경우 api별로 req body, param 같은 경우나 response에 대해서 하나 하나 지정을 해줘야 하더라구요
그렇게 직접 지정해줘야 할게 무엇이 있는지에 대해서 간단하게 많이 사용 되는것 위주로만 정리해 보았습니다.
-
일반적으로 Dto를 작성한다
export class CreateBroadcastDto { @ApiProperty({ example: '페어 프로그래밍 방송', description: '방송 제목', required: true }) title: string; @ApiProperty({ example: true, description: '화면 공유 여부', default: false }) isScreenShare: boolean; @ApiProperty({ example: true, description: '카메라 사용 여부', default: true }) isCameraOn: boolean; }
-
dto를 바로 controller 매개변수에 적용하거나
@ApiBody
데코레이터를 이용한다.@ApiBody({ type: CreateBroadcastDto, description: '방송 생성 정보' }) async createBroadcast(@Body() createBroadcastDto: CreateBroadcastDto) { return this.broadcastService.create(createBroadcastDto); }
-
response 별로 아래와 같이 데코레이터를 달아주어야 한다.
@ApiResponse({ status: 200, description: '방송 목록 조회 성공', type: BroadcastListResponseDto, isArray: true // 배열 응답인 경우 }) @ApiResponse({ status: 401, description: '인증 실패' }) @ApiResponse({ status: 500, description: '서버 에러' }) async getBroadcasts() { const broadcasts = await this.broadcastService.findAll(); return BroadcastListResponseDto.fromArray(broadcasts); }
-
아니면 response Dto를 사용할 수 있다.
****export class ApiResponseDto<T> { @ApiProperty({ example: true, description: '성공 여부' }) success: boolean; @ApiProperty({ description: '응답 데이터' }) data: T; @ApiProperty({ example: '요청이 성공적으로 처리되었습니다.', description: '응답 메시지' }) message: string; } @ApiResponse({ status: 200, description: '방송 목록 조회 성공', type: ApiResponseDto<BroadcastListResponseDto[]> }) async getBroadcasts() { const broadcasts = await this.broadcastService.findAll(); return { success: true, data: BroadcastListResponseDto.fromArray(broadcasts), message: '방송 목록 조회 성공' }; }
위에서 작성한 것처럼 swagger를 사용하여 응답을 문서화 하기에는 상당히 까다로운 방식을 거쳐야 합니다.
또한, 응답의 내용에 따라 다르게 swagger 데코레이터를 사용하여야 하기 때문에 통일성을 갖기 어렵다는 생각이 들었습니다.
응답 api문서를 일관되게 하고 코드의 중복을 제거하기 위해서 response에 대해서 커스텀 데코레이터를 만들어 보기로 했습니다. 많은 분들이 이렇게 해서 swagger 세팅을 하시는 모습이었습니다.
- 일관성: 모든 API 응답이 동일한 구조를 가집니다.
- 재사용성: 데코레이터를 통해 응답 문서화를 쉽게 재사용할 수 있습니다.
- 유지보수성: 응답 구조 변경 시 한 곳만 수정하면 됩니다.
- 가독성: Swagger UI에서 명확한 응답 구조를 확인할 수 있습니다.
-
applyDecorators
- 여러 데코레이터를 하나로 결합할 때 사용
- 복합 데코레이터를 만들때 유용
- 데코레이터는 실행 순서가 중요하므로 applyDecorators 사용시에 순서 주의
-
ApiExtraModels
- swagger 문서 생성 시 참조되어야 할 추가적인 모델(클래스, 인터페이스)를 등록하는데 사용됨
- ApiExtraModels 함수로 위에서 등록을 한 클래스만 swagger가 인식 할 수 있다.
-
getSchemaPath
- 스키마 참조 경로를 생성하는 유틸리티 함수
- 해당 함수를 사용하면 스키마에
{properties: ~~~}
형식이 아닌{ $ref: getSchemaPath(dto) }
형식으로 사용할 수 있다.
-
allOf
- 함수는 아니지만 ApiResponse 데코레이터의 schema에서 사용되는 부분
- 여러 스키마를 결합할 때 사용하는 키워드
- 여러 스키마의 모든 속성을(properties) 하나로 합치는 역할
import { applyDecorators, Type } from '@nestjs/common';
import { ApiExtraModels, ApiProperty, ApiResponse, getSchemaPath } from '@nestjs/swagger';
import { SuccessStatus } from '../responses/bases/successStatus';
const createSuccessResponseClass = (options: SuccessStatus) => {
class SuccessResponse<T> {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ example: options.status || '200' })
status: string;
@ApiProperty({ example: options.message || 'OK.' })
message: string;
data?: T;
}
return SuccessResponse;
};
export const ApiSuccessResponse = <T extends Type<any>>(options: SuccessStatus, model?: T) => {
const SuccessResponse = createSuccessResponseClass(options);
const decorator = [
ApiExtraModels(SuccessResponse),
ApiResponse({
description: options.message || 'OK.',
status: parseInt(options.status),
schema: {
allOf: model
? [
{ $ref: getSchemaPath(SuccessResponse) },
{
properties: {
data: { $ref: getSchemaPath(model) },
},
},
]
: [{ $ref: getSchemaPath(SuccessResponse) }],
},
}),
];
if (model) decorator.push(ApiExtraModels(model));
return applyDecorators(...decorator);
};
- 매개변수
- options : 응답 데이터 형식. 기존에 프로젝트에서 사용하던 클래스가 있어서 가져와서 타입정의를 해줬습니다.
- models : 응답 데이터에서 data 부분에 들어갈 dto. data가 없는 경우가 있기 때문에 옵셔널입니다.
-
createSuccessResponseClass
함수를 통해 각 API에 맞는 응답 클래스(dto)를 동적으로 생성합니다. -
createSuccessResponseClass
함수를 통해 만든 dto를ApiExtraModels
함수로 swagger에 응답 모델을 등록합니다. -
ApiResponse
함수로 응답 스키마를 정의합니다-
allOf
를 사용해서 이전에 만든 successResponse dto와 그 내부에 들어갈 data model을 결합합니다.
-
- model이 있을 경우 swagger가 인식할 수 있도록 마찬가지로 model을 swagger에 등록합니다.
-
applyDecorators
함수로 여러개의 데코레이터를 하나로 결합합니다.
import { applyDecorators } from '@nestjs/common';
import { ApiExtraModels, ApiProperty, ApiResponse, getSchemaPath } from '@nestjs/swagger';
import { ErrorStatus } from '../responses/exceptions/errorStatus';
const createErrorResponseClass = (options: ErrorStatus) => {
class ErrorResponse {
@ApiProperty({ example: options.code || 500 })
code: number;
@ApiProperty({ example: options.status || 'COMMON_5000' })
status: string;
@ApiProperty({ example: options.message || '실패' })
message: string;
}
return ErrorResponse;
};
export const ApiErrorResponse = (options: ErrorStatus) => {
const ErrorResponse = createErrorResponseClass(options);
return applyDecorators(
ApiExtraModels(ErrorResponse),
ApiResponse({
description: options.message || '실패',
status: options.code,
schema: {
allOf: [{ $ref: getSchemaPath(ErrorResponse) }],
},
}),
);
};
- 성공 응답 데코레이터와 동일한 방식으로 제작했습니다
- 오히려 실패 응답에는 data 속성이 존재하지 않아 코드가 더 간결해졌습니다.
class testDto {
@ApiProperty({
example: 1,
description: '성공 여부',
})
id: number;
@ApiProperty({
example: '전희선',
description: '성공 여부',
})
name: string;
}
@Get('/test')
@ApiTags('헤더')
@ApiSuccessResponse(SuccessStatus.OK(testDto), testDto)
@ApiSuccessResponse(SuccessStatus.NO_CONTENT())
@ApiErrorResponse(ErrorStatus.USER_NOT_FOUND)
async test() {
return SuccessStatus.OK({ id: 1, name: '희선' });
}
[[NestJS] NestJS에서 Swagger 사용법 (feat. API Documentation)](https://cdragon.tistory.com/entry/NestJS-NestJS%EC%97%90%EC%84%9C-Swagger-%EC%82%AC%EC%9A%A9%EB%B2%95-feat-API-Documentation)
- Mediasoup 포트 매핑 문제
- swagger 같은 응답 코드에 다양한 응답 보여주기
- Sudo가 계속 비밀번호를 요청함
- Docker 이미지가 너무 크다
- Git action에서 도커 이미지 빌드 시간을 단축시켜보자
- Docker compose를 이용해서 메모리 사용률을 줄여보자
- 방송 녹화 시 CPU 과부하 문제를 해결해보자
- Release 브랜치? 너 필요해?
- 로딩이 너무 짧아…!
- NestJS ORM으로 무엇을 사용해야 할까?
- WebRTC를 이용한 1:N 스트리밍 서비스에서 시그널링 서버가 필요할까?
- 실시간 채팅 구현: 인메모리 방식을 선택한 이유
- MySQL 아키텍처 개선: DB 의존성 분리와 서버 역할 명확화
- 브라우저 창이 최소화되면 비디오 송출이 안된다…!
- Mediasoup 기본 개념
- DLTS와 Signaling
- Tell, Don't Ask (TDA) 원칙이란
- VPC(Virtual Private Cloud) 학습 정리
- 순환참조: A 서비스 ‐ B 서비스 vs. A 서비스 ‐ B 레포지토리
- Dto 메서드 전략
- WebRTC란?
- 자바스크립트 패키지 매니저(npm, yarn, pnpm)
- shadcn/ui을 이용해 UI 개발 생산성 높이기
- React 이벤트 핸들러 네이밍(on vs handle)
- React-router-dom의 createBrowserRouter을 사용해보기
- fetch vs axios