Swagger (OpenAPI)
ASAPJS는 라우트 데코레이터와 DTO/TypeIs 정의로부터 OpenAPI 3.0 스펙을 자동으로 생성합니다. 별도의 Swagger 파일을 작성할 필요 없이, 코드가 곧 API 문서가 됩니다.
Swagger UI 접근
RouterModule은 부트스트랩 시 자동으로 다음 엔드포인트를 등록합니다:
| 엔드포인트 | 설명 |
|---|---|
/{basePath}/docs/swagger-ui.html | Swagger UI 인터페이스 |
/{basePath}/docs/swagger.json | 생성된 OpenAPI 3.0 JSON 스펙 |
예를 들어 basePath: 'api'인 경우:
- Swagger UI:
http://localhost:3000/api/docs/swagger-ui.html - JSON 스펙:
http://localhost:3000/api/docs/swagger.json
Swagger 설정
IConfig.swagger 객체로 Swagger UI의 정보와 동작을 설정합니다.
// src/index.ts
const config = {
name: 'My API',
port: 3000,
basePath: 'api',
extensions: ['@asapjs/sequelize'],
swagger: {
name: 'My API Documentation',
version: '1.0.0',
description: 'ASAPJS로 구축된 REST API',
scheme: 'http',
host: 'localhost:3000',
auth_url: '/api/users/login',
useAuth: true,
userObject: {
admin: 'secure-swagger-password',
},
},
// ... auth, sequelize 설정
};swagger 필드
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
name | string | Yes | Swagger UI 상단에 표시되는 API 이름 |
version | string | Yes | API 버전 문자열 (예: '1.0.0') |
description | string | Yes | API 설명 텍스트 |
scheme | 'http' | 'https' | Yes | ”Try it out” 요청에 사용할 URL 스킴 |
host | string | Yes | ”Try it out” 요청에 사용할 호스트 (예: 'localhost:3000') |
auth_url | string | No | OAuth2 Password Flow의 토큰 URL |
useAuth | boolean | No | true일 때 Swagger UI 접근에 Basic Auth를 요구 |
userObject | { [username]: password } | No | Swagger UI Basic Auth 사용자 계정 |
Basic Auth 보호
프로덕션 환경에서는 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: true와 userObject를 설정하면, Swagger UI 접근 시 브라우저 기본 인증 대화 상자가 나타납니다.
주의:
useAuth를 설정하지 않으면 다음과 같은 경고 로그가 출력됩니다:@router SWAGGER useAuth is disabled! Please add config.swagger.useAuth & config.swagger.userObject
자동 생성 원리
전체 흐름
@Get/@Post/@Put/@Delete 데코레이터
↓
RouterController.excute()
↓ body/response DTO → generateSwaggerDefaultsSet()
↓
addPaths() + addScheme() ← DocsApplication에 등록
↓
GET /docs/swagger.json 요청 시
↓
getSwaggerData(req) → 최종 OpenAPI JSON 반환DocsApplication
Swagger 데이터를 수집하고 최종 OpenAPI 스펙을 생성하는 싱글턴 클래스입니다.
// packages/router/src/swagger/index.ts
class DocsApplication {
public swaggerData = { ...defaultSwagger }; // OpenAPI 3.0 기본 템플릿
private swaggerPath = []; // 경로 정보 (addPaths로 추가)
private swaggerScheme = []; // 스키마 정보 (addScheme로 추가)
public addPaths = async (data: any) => {
this.swaggerPath.push(data);
};
public addScheme = async (data: any) => {
this.swaggerScheme.push(data);
};
public generateSwaggerData = (req: any) => {
const config = getConfig();
// IConfig.swagger에서 정보 설정
this.swaggerData.info.title = config?.swagger?.name || '';
this.swaggerData.info.version = config?.swagger?.version || '';
this.swaggerData.info.description = config?.swagger?.description || '';
// 서버 URL 설정 (요청 호스트 + config 호스트)
this.swaggerData.servers = [
{ url: `${config?.swagger?.scheme}://${req.headers.host}/${config?.basePath}` },
{ url: `${config?.swagger?.scheme}://${config?.swagger?.host}/${config?.basePath}` },
];
// 경로와 스키마를 OpenAPI 구조에 매핑
this.swaggerPath.forEach((v) => {
if (!this.swaggerData.paths[v.path]) this.swaggerData.paths[v.path] = {};
const { path: _, method: __, ...props } = v;
this.swaggerData.paths[v.path][v.method?.toLowerCase()] = props;
});
this.swaggerScheme.forEach((v) => {
this.swaggerData.components.schemas[v.name] = v.data;
});
};
}
export const { addPaths, addScheme, getSwaggerData } = new DocsApplication();기본 OpenAPI 템플릿
DocsApplication은 다음 기본 구조에서 시작합니다:
// packages/router/src/assets/default-swagger.json
{
"openapi": "3.0.0",
"servers": [],
"info": { "version": "", "title": "", "description": "" },
"paths": {},
"components": {
"schemas": {},
"securitySchemes": {
"bearerAuth": { "type": "http", "scheme": "bearer" },
"OAuthLogin": {
"type": "oauth2",
"flows": {
"password": { "tokenUrl": "/auth/login", "scopes": {} }
}
}
}
},
"security": [
{ "OAuthLogin": [] },
{ "bearerAuth": [] }
]
}내장된 보안 스킴:
- bearerAuth —
Authorization: Bearer <token>헤더 방식 - OAuthLogin — OAuth2 Password Flow (Swagger UI에서 직접 로그인 가능)
라우트 데코레이터 → Swagger 매핑
데코레이터 옵션과 Swagger 필드 매핑
@Post('/register', {
title: '회원 가입', // → summary
description: '새 사용자 등록', // → description
deprecated: false, // → deprecated
body: CreateUserDto, // → requestBody
bodyContentType: 'application/json', // → requestBody content type
query: PaginationQueryDto, // → parameters (in: query)
response: UserInfoDto, // → responses.200
auth: true, // → 런타임 JWT 미들웨어만 제어 (Swagger 스키마에 미반영)
})| 데코레이터 옵션 | OpenAPI 필드 | 설명 |
|---|---|---|
title | summary | 엔드포인트 한 줄 설명 |
description | description | 상세 설명 |
deprecated | deprecated | 더 이상 사용되지 않는 엔드포인트 표시 |
body | requestBody | 요청 바디 스키마 (DTO 또는 TypeIs) |
bodyContentType | requestBody.content | application/json 또는 multipart/form-data |
query | parameters | 쿼리 파라미터 스키마 (DTO) |
response | responses.200 | 성공 응답 스키마 (DTO 또는 TypeIs) |
auth | (Swagger 미반영) | 런타임 jwtVerification 미들웨어만 제어. Swagger에는 per-endpoint security가 추가되지 않음 |
auth와 Swagger security의 관계:auth: true는 런타임에서jwtVerification미들웨어를 활성화하여 JWT 토큰 검증을 수행합니다. 그러나 Swagger 스펙에는 per-endpointsecurity필드가 추가되지 않습니다. 대신default-swagger.json에 정의된 전역 security (bearerAuth+OAuthLogin)가 모든 엔드포인트에 일괄 적용됩니다. 즉, Swagger UI에서는auth값과 관계없이 모든 엔드포인트에 동일한 인증 UI가 표시됩니다.
URL 파라미터 자동 처리
경로에 :param 형태의 파라미터가 있으면 자동으로 OpenAPI path parameter로 변환됩니다:
// packages/router/src/express/router.ts 에서 처리
@Get('/:id', { title: '사용자 상세', response: UserInfoDto })
async getUser({ path }: ExecuteArgs) {
// path.id로 접근 가능
}
// Swagger 변환 결과:
// path: /users/{id}
// parameters: [{ in: 'path', name: 'id', required: true, schema: { type: 'string' } }]DTO → Swagger 스키마 변환
DTO 클래스 방식
ExtendableDto를 상속한 DTO는 swagger() 메서드로 $ref 참조를 생성하고, generateScheme()으로 속성 스키마를 생성합니다.
// packages/sequelize/src/dto/ExtendableDto.ts
export default class ExtendableDto {
public generateScheme = () => {
const types = getTypesData(this);
const properties = Object.keys(types).reduce((p, key) => {
p[key] = types[key].toSwagger?.() || null;
return p;
}, {});
return { type: 'object', properties };
};
public swagger = () => {
return { $ref: `#/components/schemas/${this.constructor.name}` };
};
}DTO 예시와 생성되는 Swagger 스키마:
// DTO 정의
export default class CreateUserDto extends ExtendableDto {
@TypeIs.STRING({ comment: '이메일' })
email: string;
@TypeIs.PASSWORD({ comment: '비밀번호' })
password: string;
@TypeIs.STRING({ comment: '사용자 이름' })
name: string;
}
// 생성되는 Swagger 스키마:
// {
// "CreateUserDto": {
// "type": "object",
// "properties": {
// "email": { "type": "string", "description": "이메일" },
// "password": { "type": "string", "format": "password", "description": "비밀번호" },
// "name": { "type": "string", "description": "사용자 이름" }
// }
// }
// }TypeIs 팩토리 방식
DTO 클래스 외에 TypeIs 팩토리 함수를 직접 사용할 수도 있습니다:
// 배열 응답
@Get('/', {
response: TypeIs.ARRAY(UserInfoDto), // → { type: 'array', items: { $ref: '...' } }
})
// 페이지네이션 응답
@Get('/', {
response: TypeIs.PAGING(PostInfoDto), // → 페이지네이션 래핑 스키마
})추가 엔드포인트
RouterModule은 Swagger 외에도 개발에 유용한 엔드포인트를 자동 등록합니다:
| 엔드포인트 | 조건 | 설명 |
|---|---|---|
/{basePath} | 항상 | 서버 동작 확인 메시지 |
/health-check | 항상 | DB 연결 상태 확인 (SELECT 1) |
/{basePath}/sync | @asapjs/sequelize 확장 활성화 시 | DB 테이블 동기화 (modelsSync()) |
/{basePath}/docs/generate-dbml | @asapjs/sequelize 확장 활성화 시 | DBML 스키마 생성 |
관련 문서
- Bootstrap —
IConfig.swagger설정 상세 - Routing — 라우트 데코레이터와
RouterController - Deployment — 프로덕션 Swagger 보안 설정