Skip to Content
문서가이드Swagger 문서 자동 생성

Swagger 문서 자동 생성

ASAPJS는 라우트 데코레이터에 선언된 정보로부터 OpenAPI 3.0 (Swagger) 문서를 자동으로 생성합니다. 별도의 스키마 파일을 작성할 필요 없이, 컨트롤러의 @Get, @Post, @Put, @Delete 데코레이터 옵션과 DTO/TypeIs 선언만으로 완전한 API 문서가 만들어집니다.

동작 원리

Swagger 문서 생성은 세 단계로 이루어집니다:

  1. 라우트 등록 시점 — 컨트롤러의 registerRoutes()가 호출되면, 각 라우트의 excute 메서드가 IOptions로부터 Swagger path 정보와 스키마를 생성하여 addPaths/addScheme으로 등록
  2. 스키마 수집@asapjs/coreaddScheme이 전역 scheme 레지스트리를 관리하고, DocsApplication이 path를 수집
  3. JSON 생성 — 첫 요청 시 getSwaggerData가 수집된 데이터를 OpenAPI 3.0 형식으로 조합

Swagger 구성 설정

Application.run(config)에 전달하는 설정 객체의 swagger 필드로 Swagger 문서 정보를 구성합니다.

Application.run({ name: 'my-api', basePath: 'api', swagger: { name: 'My API', version: '1.0.0', description: 'API 문서 설명', scheme: 'https', host: 'api.example.com', auth_url: '/auth/login', // 선택: Swagger UI 접근 제어 useAuth: true, userObject: { admin: 'password123' }, }, // ... });
설정타입설명
namestringSwagger UI에 표시될 API 이름
versionstringAPI 버전
descriptionstringAPI 설명
schemestringURL 스킴 (http 또는 https)
hoststringAPI 호스트 도메인
auth_urlstringOAuth2 토큰 발급 URL
useAuthbooleanSwagger UI에 Basic Auth 보호 적용 여부
userObjectobjectBasic Auth 사용자 계정 ({ username: password })

라우트 데코레이터와 Swagger

IOptions와 Swagger 매핑

라우트 데코레이터의 IOptions 각 필드가 Swagger 문서의 어떤 부분을 생성하는지:

@Post('/register', { title: '회원 가입', // → summary description: '새로운 사용자 등록', // → description deprecated: false, // → deprecated auth: true, // → 런타임 JWT 미들웨어 제어 (Swagger에는 전역 security 적용) body: CreateUserDto, // → requestBody schema bodyContentType: 'application/json', // → requestBody content-type query: PaginationQueryDto, // → parameters (in: query) response: UserInfoDto, // → responses.200 schema })
IOptions 필드Swagger 위치설명
titlesummary엔드포인트 제목
descriptiondescription상세 설명
deprecateddeprecated비권장 표시
bodyrequestBody.content.*.schema요청 본문 스키마
bodyContentTyperequestBody.contentContent-Type (application/json 기본)
queryparameters[] (in: query)쿼리 파라미터
responseresponses.200.content.*.schema응답 스키마

Path 파라미터 자동 감지

라우트 경로에 :paramName 형태의 Express 파라미터가 있으면, 자동으로 Swagger path 파라미터로 변환됩니다.

// 내부 변환 로직 (packages/router/src/express/router.ts) // '/posts/:postId' → '/posts/{postId}' const realPath = swaggerPath .split('/') .map((v) => (v.includes(':') ? `{${v.replace(':', '')}}` : v)) .join('/'); // path 파라미터를 Swagger parameters에 추가 swaggerPath.split('/').forEach((v) => v.includes(':') && parameters.push({ in: 'path', name: v.replace(':', ''), required: true, schema: { type: 'string' }, }) );

예를 들어 @Get('/:postId', { ... })는 Swagger에서 다음과 같이 표시됩니다:

parameters: - in: path name: postId required: true schema: type: string

DTO → Swagger 스키마 변환

DTO 클래스 기반 스키마

DTO 클래스를 body, query, response에 전달하면, ExtendableDto.swagger() 메서드가 호출되어 $ref 기반 스키마 참조가 생성됩니다.

// ExtendableDto.generateScheme() — 각 필드의 toSwagger()를 호출하여 스키마 생성 { type: 'object', properties: { id: { type: 'integer', format: 'int32', description: 'User ID' }, email: { type: 'string', description: 'Email' }, name: { type: 'string', description: 'Display name' }, } } // ExtendableDto.swagger() — $ref 형태로 반환 { $ref: '#/components/schemas/UserInfoDto' }

TypeIs 기반 스키마

DTO 대신 TypeIs.* 함수를 직접 사용할 수도 있습니다:

// 단일 타입 response: TypeIs.BOOLEAN() // 배열 응답 response: TypeIs.ARRAY(PostInfoDto) // 페이지네이션 응답 response: TypeIs.PAGING(PostInfoDto)

TypeIs.ARRAYTypeIs.PAGING은 DTO 클래스 또는 TypeIs.* 함수를 인자로 받아 복합 스키마를 생성합니다. 내부적으로 isClass 검사를 통해 DTO와 TypeIs를 구분합니다.

body 스키마 생성 과정

RouterControllerexcute 메서드에서 body 스키마가 생성되는 과정:

// packages/router/src/express/router.ts (간소화) if (body !== undefined) { if (isClass(body)) { // DTO 클래스 → swagger() 호출 → $ref 스키마 const data = new body().swagger(); result.requestBody = this.generateRequestBody( this.generateDtoScheme(data, 'Body'), bodyContentType ); } else { // TypeIs.* 함수 → toSwagger() 호출 const type = body(); if (['array', 'paging'].includes(type?.__name)) { result.requestBody = this.generateRequestBody( this.generateDtoScheme(type?.toSwagger?.(), 'Body'), bodyContentType ); } else { result.requestBody = this.generateRequestBody( this.generateTypeScheme(body, 'Body'), bodyContentType ); } } }

DocsApplication 내부 구조

Swagger 데이터는 DocsApplication 싱글턴 클래스에서 관리됩니다.

// @asapjs/core — 전역 Swagger 스키마 레지스트리 export function addScheme(data: { name: string; data: any }): void { ... } export function generateSchemeRefWithName(name: string): string { ... } // @asapjs/router/src/swagger/index.ts — path 및 최종 JSON 관리 class DocsApplication { public swaggerData = { ...defaultSwagger }; // OpenAPI 3.0 기본 구조 private swaggerPath = []; // 등록된 path 정보 // 라우트 등록 시 호출 public addPaths = async (data: any) => { ... }; // 첫 요청 시 최종 Swagger JSON 조합 (core의 스키마 레지스트리 사용) public generateSwaggerData = (req: any) => { ... }; // 캐시된 데이터 반환 (없으면 generate 호출) public getSwaggerData = (req: any) => { ... }; } export const { addPaths, getSwaggerData } = new DocsApplication(); export { addScheme, generateSchemeRefWithName } from '@asapjs/core';

path 정렬 규칙

Swagger 문서의 엔드포인트는 다음 순서로 정렬됩니다:

  1. tags (컨트롤러의 tag 값) 기준 알파벳 순
  2. 같은 tag 내에서 path 기준 알파벳 순
  3. 같은 path 내에서 HTTP 메서드 순서: GETPOSTPUTDELETE

Swagger UI 접근

기본 엔드포인트

엔드포인트설명
/{basePath}/docs/swagger-ui.htmlSwagger UI 웹 인터페이스
/{basePath}/docs/swagger.jsonOpenAPI 3.0 JSON 스펙

basePathapi로 설정된 경우:

  • Swagger UI: http://localhost:3000/api/docs/swagger-ui.html
  • JSON 스펙: http://localhost:3000/api/docs/swagger.json

Basic Auth 보호

config.swagger.useAuthtrue로 설정하면 Swagger UI에 Basic Auth가 적용됩니다.

// packages/router/src/router/index.ts (간소화) if (config?.swagger?.useAuth && config?.swagger?.userObject) { this.app.use( `/${basePath}/docs/swagger-ui.html`, basicAuth({ users: config.swagger.userObject, // { admin: 'password' } challenge: true, realm: 'Developer', }), swaggerUi.serveFiles(undefined, options), swaggerUi.setup(undefined, options), ); }

useAuth가 설정되지 않으면 경고 로그가 출력됩니다:

@router SWAGGER useAuth is disabled! Please add config.swagger.useAuth & config.swagger.userObject

보안 스킴

기본 Swagger 구조에 두 가지 보안 스킴이 포함되어 있습니다:

{ "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer" }, "OAuthLogin": { "type": "oauth2", "flows": { "password": { "tokenUrl": "/auth/login", "scopes": {} } } } } } }

auth 옵션은 런타임에서 jwtVerification 미들웨어의 적용 여부를 제어합니다. Swagger 문서에는 default-swagger.json에 정의된 전역 security 설정(bearerAuth, OAuthLogin)이 모든 엔드포인트에 동일하게 적용되며, auth 값에 따른 per-endpoint security 구분은 생성되지 않습니다.

완전한 예시

다음은 example 앱의 PostController에서 Swagger 문서가 자동 생성되는 과정입니다:

import { RouterController, Get, Post, Put, Delete, ExecuteArgs } from '@asapjs/router'; import { PaginationQueryDto, TypeIs } from '@asapjs/sequelize'; import CreatePostDto from '../dto/CreatePostDto'; import UpdatePostDto from '../dto/UpdatePostDto'; import PostInfoDto from '../dto/PostInfoDto'; export default class PostController extends RouterController { public tag = 'Post'; // Swagger 태그 그룹 public basePath = '/posts'; // URL 접두사 constructor() { super(); this.registerRoutes(); // 여기서 Swagger 스키마도 함께 등록 this.postService = new PostApplication(); } // Swagger: GET /posts // - query parameters: page, limit // - response: PostInfoDto_PAGING 스키마 @Get('/', { title: '게시글 목록 조회', description: '게시글 목록을 조회합니다', query: PaginationQueryDto, response: TypeIs.PAGING(PostInfoDto), }) async getPosts({ paging }: ExecuteArgs) { ... } // Swagger: POST /posts // - auth: true → 런타임 JWT 미들웨어 적용 (Swagger에는 전역 security) // - requestBody: CreatePostDto 스키마 // - response: PostInfoDto 스키마 @Post('/', { title: '게시글 작성', auth: true, body: CreatePostDto, response: PostInfoDto, }) async createPost({ body, user }: ExecuteArgs) { ... } // Swagger: GET /posts/{postId} // - path parameter: postId (자동 감지) // - response: PostInfoDto 스키마 @Get('/:postId', { title: '게시글 상세 조회', response: PostInfoDto, }) async getPost({ path }: ExecuteArgs) { ... } // Swagger: DELETE /posts/{postId} // - auth: true → 런타임 JWT 미들웨어 적용 (Swagger에는 전역 security) // - path parameter: postId (자동 감지) @Delete('/:postId', { title: '게시글 삭제', auth: true, }) async deletePost({ path, user }: ExecuteArgs) { ... } }

registerRoutes()가 호출되면 각 데코레이터의 옵션이 처리되어:

  • addPaths로 4개의 Swagger path 엔트리가 등록됩니다
  • addScheme으로 CreatePostDto, PostInfoDto, UpdatePostDto, PostInfoDto_PAGING 스키마가 등록됩니다
  • tag: 'Post'로 인해 모든 엔드포인트가 “Post” 그룹으로 묶입니다

관련 문서

  • RoutingIOptions 인터페이스와 라우트 데코레이터
  • DTOsExtendableDto, generateScheme(), swagger()
  • 페이지네이션TypeIs.PAGING 스키마 구조
Last updated on