Swagger를 이용한 API 문서화
안녕하세요! 지난 절에서 NestJS 애플리케이션에서 DTO와 유효성 검사를 통해 API의 견고함을 확보하는 방법을 알아보았습니다. 이번 절에서는 개발된 RESTful API의 활용도를 극대화하고, 협업 효율을 높이는 데 필수적인 API 문서화에 대해 다루겠습니다. 특히 NestJS에서 공식적으로 지원하는 Swagger(OpenAPI) 를 활용한 문서화 방법을 상세히 살펴보겠습니다.
API 문서는 프론트엔드 개발자, 다른 백엔드 팀, 심지어 미래의 나 자신에게도 API의 사용법을 명확하게 알려주는 가이드 역할을 합니다. 수동으로 문서를 작성하는 것은 번거롭고, API가 변경될 때마다 업데이트하기 어렵다는 단점이 있습니다. Swagger는 이러한 문제를 해결하기 위해 코드에서 API 명세를 자동으로 생성하고, 심지어 웹 UI를 통해 테스트까지 할 수 있는 강력한 도구입니다.
Swagger(OpenAPI)란 무엇인가?
Swagger는 API를 설계, 빌드, 문서화, 사용하는 데 도움이 되는 오픈소스 도구들의 생태계를 말합니다. 현재는 OpenAPI Specification(OAS) 이라는 이름으로 표준화되어 있으며, 이는 RESTful API를 언어 독립적으로 설명하기 위한 기계 판독 가능한(machine-readable) 인터페이스 파일 형식을 정의합니다.
Swagger 사용의 주요 이점
- 자동 문서화: 코드에 주석이나 데코레이터를 추가하면 API 명세가 자동으로 생성됩니다. 이는 수동 문서 작성의 번거로움을 줄이고, 코드 변경 시 문서 업데이트를 용이하게 합니다.
- 대화형 UI: 웹 기반의 사용자 인터페이스(Swagger UI)를 제공하여 API 엔드포인트, 요청/응답 스키마, 파라미터 등을 시각적으로 보여주고, 실제 API 호출을 테스트해볼 수도 있습니다.
- 개발자 경험(DX) 향상: 프론트엔드 개발자가 백엔드 API를 쉽게 이해하고 사용할 수 있도록 돕습니다.
- 코드 생성: OpenAPI 명세를 기반으로 클라이언트 SDK나 서버 스텁 코드를 자동으로 생성할 수 있습니다.
- 표준화: OpenAPI는 널리 인정받는 표준이므로, 다른 도구나 플랫폼과의 연동이 용이합니다.
NestJS에 Swagger 통합하기
NestJS는 @nestjs/swagger
패키지를 통해 Swagger를 손쉽게 통합할 수 있도록 지원합니다.
단계 1: 필요한 패키지 설치
npm install @nestjs/swagger swagger-ui-express
@nestjs/swagger
: NestJS와 Swagger를 통합하는 공식 패키지입니다.swagger-ui-express
: Express 기반 앱에서 Swagger UI를 서빙하기 위한 미들웨어입니다. NestJS는 내부적으로 Express를 사용하므로 이 패키지가 필요합니다.
단계 2: main.ts
에 Swagger 설정 추가
애플리케이션의 진입점인 main.ts
파일에서 Swagger 문서를 초기화하고 설정합니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; // Swagger 관련 모듈 임포트
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 전역 유효성 검사 파이프 설정 (이전 절에서 다룸)
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
disableErrorMessages: process.env.NODE_ENV === 'production',
}));
// Swagger 설정 시작
const config = new DocumentBuilder()
.setTitle('Users API Example') // API 문서의 제목
.setDescription('The Users API description with CRUD operations.') // API 문서 설명
.setVersion('1.0') // API 버전
.addTag('users', 'User related endpoints') // API에 태그 추가 (컨트롤러 그룹화에 사용)
.addBearerAuth( // JWT 인증을 위한 Bearer 토큰 설정
{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT', in: 'header', name: 'JWT' },
'access-token' // 이 보안 정의의 이름 (나중에 @ApiSecurity 데코레이터에서 사용)
)
.build();
// 설정 객체를 기반으로 Swagger 문서 객체 생성
const document = SwaggerModule.createDocument(app, config);
// /api 경로에 Swagger UI를 서빙하도록 설정
// 이 경로로 접속하면 API 문서를 웹에서 볼 수 있습니다.
SwaggerModule.setup('api', app, document);
// Swagger 설정 끝
await app.listen(3000);
}
bootstrap();
코드 설명
DocumentBuilder()
: API 문서의 기본 정보(제목, 설명, 버전 등)를 설정하는 빌더 클래스입니다.addTag()
: API 엔드포인트를 논리적인 그룹으로 묶는 데 사용되는 태그를 추가합니다. 컨트롤러에@ApiTags()
데코레이터와 함께 사용됩니다.addBearerAuth()
: JWT와 같은 Bearer 토큰 인증 방식을 문서에 추가합니다. 이를 통해 Swagger UI에서 토큰을 입력하고 보호된 API를 테스트할 수 있습니다.access-token
은 이 보안 스키마의 이름이며, 나중에 API 작업에 이 보안 스키마를 적용할 때 사용합니다.SwaggerModule.createDocument(app, config)
: NestJS 애플리케이션의 메타데이터와DocumentBuilder
로 생성한 설정을 기반으로 OpenAPI 문서 객체를 생성합니다.SwaggerModule.setup('api', app, document)
:/api
경로에 생성된 Swagger UI를 호스팅합니다. 이제http://localhost:3000/api
로 접속하면 Swagger UI를 볼 수 있습니다.
단계 3: DTO 및 컨트롤러에 Swagger 데코레이터 적용
API의 구조와 동작을 Swagger 문서에 상세하게 표현하기 위해 DTO 클래스와 컨트롤러 메서드에 @nestjs/swagger
데코레이터를 적용합니다.
import { IsString, IsEmail, IsNotEmpty, IsInt, Min, Max } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger'; // ApiProperty 임포트
export class CreateUserDto {
@ApiProperty({ description: '사용자 이름', example: '홍길동' }) // Swagger UI에 표시될 속성 정보
@IsString({ message: '이름은 문자열이어야 합니다.' })
@IsNotEmpty({ message: '이름은 필수 항목입니다.' })
name: string;
@ApiProperty({ description: '사용자 이메일 (고유)', example: 'hong.gd@example.com' })
@IsEmail({}, { message: '유효한 이메일 형식이 아닙니다.' })
@IsNotEmpty({ message: '이메일은 필수 항목입니다.' })
email: string;
@ApiProperty({ description: '사용자 나이', example: 30, minimum: 0, maximum: 150 })
@IsInt({ message: '나이는 정수여야 합니다.' })
@Min(0, { message: '나이는 0보다 커야 합니다.' })
@Max(150, { message: '나이는 150보다 작거나 같아야 합니다.' })
age: number;
}
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
import { ApiProperty } from '@nestjs/swagger';
// PartialType은 CreateUserDto의 모든 필드를 선택적(optional)으로 만들고,
// 해당 필드에 대한 @ApiProperty() 정의도 자동으로 상속합니다.
export class UpdateUserDto extends PartialType(CreateUserDto) {}
@ApiProperty()
: DTO의 각 속성에 대한 설명, 예시 값, 유효성 규칙(최소/최대 등)을 Swagger UI에 표시합니다.
import { Controller, Get, Post, Body, Param, Patch, Delete, HttpCode, HttpStatus, NotFoundException } from '@nestjs/common';
import { UsersService, User } from './users.service'; // User 인터페이스 임포트
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import {
ApiTags, // 컨트롤러에 태그를 지정
ApiOperation, // API 작업에 대한 설명
ApiResponse, // 특정 HTTP 상태 코드에 대한 응답 설명
ApiParam, // 경로 파라미터 설명
ApiBody, // 요청 본문 설명
ApiSecurity, // 보안 스키마 적용
} from '@nestjs/swagger';
@ApiTags('users') // 이 컨트롤러의 모든 API를 'users' 태그로 그룹화합니다.
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@ApiOperation({ summary: '모든 사용자 조회', description: '등록된 모든 사용자 목록을 반환합니다.' })
@ApiResponse({ status: 200, description: '성공적으로 모든 사용자를 반환합니다.', type: [User] }) // User[] 타입을 명시하여 응답 스키마 표시
findAll(): User[] { // 반환 타입 명시
return this.usersService.findAll();
}
@Get(':id')
@ApiOperation({ summary: '특정 사용자 조회', description: 'ID를 사용하여 특정 사용자를 조회합니다.' })
@ApiParam({ name: 'id', description: '조회할 사용자의 고유 ID', example: 1 })
@ApiResponse({ status: 200, description: '성공적으로 사용자를 반환합니다.', type: User })
@ApiResponse({ status: 404, description: '사용자를 찾을 수 없습니다.' })
findOne(@Param('id') id: string): User {
const user = this.usersService.findOne(+id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
@Post()
@ApiOperation({ summary: '새 사용자 생성', description: '새로운 사용자 계정을 생성합니다.' })
@ApiBody({ type: CreateUserDto, description: '생성할 사용자 정보' }) // 요청 본문의 타입을 명시
@ApiResponse({ status: 201, description: '성공적으로 사용자 생성.', type: User })
@ApiResponse({ status: 400, description: '유효하지 않은 요청 본문입니다.' })
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto): User {
return this.usersService.create(createUserDto);
}
@Patch(':id')
@ApiOperation({ summary: '사용자 정보 부분 수정', description: 'ID를 사용하여 특정 사용자의 정보를 부분적으로 수정합니다.' })
@ApiParam({ name: 'id', description: '수정할 사용자의 고유 ID' })
@ApiBody({ type: UpdateUserDto, description: '수정할 사용자 정보 (일부만 포함 가능)' })
@ApiResponse({ status: 200, description: '성공적으로 사용자 정보 수정.', type: User })
@ApiResponse({ status: 404, description: '사용자를 찾을 수 없습니다.' })
@ApiResponse({ status: 400, description: '유효하지 않은 요청 본문입니다.' })
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto): User {
const updatedUser = this.usersService.update(+id, updateUserDto);
if (!updatedUser) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return updatedUser;
}
@Delete(':id')
@ApiOperation({ summary: '사용자 삭제', description: 'ID를 사용하여 특정 사용자를 삭제합니다.' })
@ApiParam({ name: 'id', description: '삭제할 사용자의 고유 ID' })
@ApiResponse({ status: 204, description: '성공적으로 사용자 삭제 (응답 본문 없음).' })
@ApiResponse({ status: 404, description: '사용자를 찾을 수 없습니다.' })
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id') id: string): void { // void로 반환 타입을 명시
const removed = this.usersService.remove(+id);
if (!removed) {
throw new NotFoundException(`User with ID ${id} not found`);
}
}
}
새로운 데코레이터 설명
@ApiTags('users')
: 이 컨트롤러의 모든 엔드포인트를 Swagger UI에서 'users'라는 태그(그룹) 아래에 표시합니다.@ApiOperation({ summary: '...', description: '...' })
: API 작업의 간략한 요약과 자세한 설명을 제공합니다.@ApiResponse({ status: ..., description: '...', type: ... })
: 특정 HTTP 상태 코드(예: 200, 201, 400, 404)에 대한 예상 응답을 문서화합니다.type
속성에 DTO나 엔티티 클래스를 지정하면 해당 응답의 스키마를 Swagger UI에 자동으로 표시합니다.@ApiParam({ name: 'id', description: '...', example: ... })
: 경로 파라미터(예::id
)에 대한 설명과 예시 값을 제공합니다.@ApiBody({ type: CreateUserDto, description: '...' })
:POST
나PATCH
요청의 본문(Body)에 대한 설명을 제공하고, 어떤 DTO 타입을 사용하는지 명시하여 해당 DTO의 스키마를 Swagger UI에 표시합니다.@ApiSecurity('access-token')
: (선택 사항) 만약 이전에DocumentBuilder
에서addBearerAuth('access-token', ...)
로 정의한 JWT 보안 스키마를 특정 엔드포인트에 적용하고 싶다면 이 데코레이터를 사용합니다. 이는 해당 엔드포인트에 '자물쇠' 아이콘이 표시되어 인증이 필요함을 나타냅니다.
users.service.ts
의 User
인터페이스를 Export
users.service.ts
에서 User
인터페이스를 export
해야 users.controller.ts
에서 type: User
와 같이 Swagger 데코레이터에서 사용할 수 있습니다.
import { Injectable } from '@nestjs/common';
export interface User { // export 추가
id: number;
name: string;
email: string;
age: number; // age 필드도 추가
}
@Injectable()
export class UsersService {
// ... 기존 코드
}
Swagger UI 확인
애플리케이션을 실행합니다. npm run start:dev
웹 브라우저를 열고 http://localhost:3000/api
로 접속합니다.
이제 여러분은 아름답게 문서화된 API 페이지를 보게 될 것입니다.
- API 엔드포인트 목록:
users
태그 아래에 모든 사용자 관련 API가 그룹화되어 있습니다. - 각 엔드포인트의 상세 정보:
summary
,description
,parameters
,request body
,responses
등이 상세하게 표시됩니다. - 모델 스키마:
Schemas
섹션에서CreateUserDto
,UpdateUserDto
,User
등의 DTO/엔티티 구조를 시각적으로 확인할 수 있습니다. - Try it out: 각 엔드포인트 옆의 "Try it out" 버튼을 클릭하면 실제 요청을 보내보고 응답을 확인할 수 있습니다.
- Authorize: 오른쪽 상단에 'Authorize' 버튼이 있다면, JWT를 입력하여 보호된 API를 테스트할 수 있습니다.
Swagger를 NestJS에 통합하는 것은 API 개발 워크플로우를 크게 개선합니다. 자동 문서화와 대화형 UI는 프론트엔드와 백엔드 간의 협업을 원활하게 하고, API 변경에 따른 문서 업데이트 부담을 줄여줍니다. 또한, API 자체의 명세가 명확해져 유지보수성도 향상됩니다.
이것으로 5장 "REST API 개발"의 세 번째 절을 마칩니다.