라우팅
ASAPJS의 라우팅은 Express.js 기반으로 구축되며, TypeScript 메서드 데코레이터를 사용하여 컨트롤러 클래스에 HTTP 라우트를 직접 선언합니다. 각 데코레이터는 Express 라우트 핸들러와 해당 Swagger 문서 항목을 동시에 등록합니다.
HTTP 메서드 데코레이터
@asapjs/router에서 네 가지 HTTP 데코레이터를 모두 임포트합니다:
import { Get, Post, Put, Delete } from '@asapjs/router';시그니처
function Get(path: string, options: IOptions): MethodDecorator
function Post(path: string, options: IOptions): MethodDecorator
function Put(path: string, options: IOptions): MethodDecorator
function Delete(path: string, options: IOptions): MethodDecorator| 파라미터 | 타입 | 설명 |
|---|---|---|
path | string | 컨트롤러의 basePath에 대한 상대 URL 경로 세그먼트. Express 경로 파라미터를 지원합니다 (예: '/:id'). basePath 자체와 매칭하려면 '/'를 사용합니다. |
options | IOptions | Swagger 생성, 인증, body/query 형태, 라우트별 미들웨어를 위한 라우트 설정 옵션. |
IOptions 인터페이스
IOptions는 IPreference에서 내부 필드인 path와 excute를 제거한 형태입니다. 모든 필드는 선택 사항입니다.
interface IOptions {
title?: string;
description?: string;
deprecated?: boolean;
summary?: string;
auth?: boolean;
body?: DtoOrTypeIs;
bodyContentType?: 'application/json' | 'multipart/form-data';
query?: DtoOrTypeIs;
response?: DtoOrTypeIs;
errors?: ErrorCreator[];
middleware?: any[];
}| 필드 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title | string | '' | UI에 표시되는 Swagger 오퍼레이션 요약 레이블. |
description | string | '' | Swagger 오퍼레이션 상세 화면에 렌더링되는 긴 설명. |
summary | string | — | Swagger summary 필드의 별칭. |
deprecated | boolean | false | true이면 Swagger UI에서 해당 오퍼레이션을 deprecated로 표시합니다. |
auth | boolean | false | true이면 jwtVerification 미들웨어가 유효한 Bearer 토큰을 요구합니다. 인증을 참고하세요. |
body | DtoOrTypeIs | — | 요청 바디를 설명하는 DTO 클래스 또는 TypeIs.* 표현식. Swagger requestBody 스키마 생성에 사용됩니다. |
bodyContentType | 'application/json' | 'multipart/form-data' | 'application/json' | 요청 바디의 Content-Type. 파일 업로드 시 'multipart/form-data'로 설정합니다. |
query | DtoOrTypeIs | — | 쿼리 스트링 파라미터를 설명하는 DTO 클래스. Swagger 쿼리 파라미터 항목 생성에 사용됩니다. |
response | DtoOrTypeIs | — | HTTP 200 응답 형태를 설명하는 DTO 클래스 또는 TypeIs.* 표현식. |
errors | ErrorCreator[] | — | 이 라우트에서 발생할 수 있는 에러 목록. @asapjs/error의 error() 팩토리로 생성합니다. Swagger에 에러 응답 스키마(status, errorCode, message, data)를 자동 등록합니다. |
middleware | any[] | [] | 이 라우트에만 적용되는 추가 Express 미들웨어 함수 배열. JWT 검증 이후, 핸들러 이전에 실행됩니다. |
RouterController 기반 클래스
모든 컨트롤러는 RouterController를 상속해야 합니다. 기반 클래스는 Express Router 인스턴스를 생성하고, 데코레이터로 등록된 모든 라우트를 해당 라우터에 바인딩하는 registerRoutes() 메서드를 제공합니다.
import { RouterController } from '@asapjs/router';클래스 정의
class RouterController {
public basePath: string; // default: '/'
public tag: string; // Swagger 태그 그룹 레이블
public expressRouter: Router; // 내부 Express Router
constructor();
protected registerRoutes(): void;
}| 멤버 | 타입 | 설명 |
|---|---|---|
basePath | string | 이 컨트롤러의 모든 라우트에 대한 URL 접두사. 서브클래스에서 재정의합니다 (예: '/users'). 기본값은 '/'. |
tag | string | 이 컨트롤러의 모든 라우트를 그룹화하는 Swagger 태그. 서브클래스에서 재정의합니다 (예: 'User'). |
expressRouter | Router | Express Router 인스턴스. RouterPlugin이 컨트롤러를 마운트할 때 내부적으로 사용합니다. |
registerRoutes() | () => void | 데코레이터가 누적한 모든 라우트를 읽어 expressRouter에 등록합니다. 서브클래스 생성자에서 반드시 호출해야 하며, 보통 super() 직후에 호출합니다. |
서브클래스 패턴
export default class UserController extends RouterController {
public tag = 'User';
public basePath = '/users';
private userService: UserApplication;
constructor() {
super();
this.registerRoutes(); // 생성자에서 항상 호출
this.userService = new UserApplication();
}
// ... 데코레이터가 적용된 메서드들
}서비스를 인스턴스화하기 전에 registerRoutes()를 호출하여, 첫 번째 요청이 도착하기 전에 라우트 메타데이터가 완전히 바인딩되도록 합니다.
전체 예제
다음 컨트롤러는 참조 구현에서 발췌한 것으로, 데코레이터, errors 옵션, 제네릭 ExecuteArgs 사용 방법을 보여줍니다.
import { RouterController, Get, Post, ExecuteArgs } from '@asapjs/router';
import { UserApplication } from '../application/UserApplication';
import CreateUserDto from '../dto/CreateUserDto';
import GetUserListQueryDto from '../dto/GetUserListQueryDto';
import UserDto from '../dto/UserDto';
import { UserErrors } from '../errors/UserErrors';
export default class UserController extends RouterController {
public basePath = '/users';
public tag = 'users';
private userService: UserApplication;
constructor() {
super();
this.registerRoutes();
this.userService = new UserApplication();
}
@Get('/', {
title: '사용자 목록 조회',
description: '페이지네이션을 지원하는 사용자 목록을 조회합니다.',
query: GetUserListQueryDto,
response: UserDto,
})
public getUserList = async ({ paging, user }: ExecuteArgs<{}, GetUserListQueryDto, {}>) => {
const result = await this.userService.list(paging, user);
return { result };
};
@Get('/:userId', {
title: '사용자 상세 조회',
description: '특정 사용자의 상세 정보를 조회합니다.',
response: UserDto,
errors: [UserErrors.NOT_FOUND],
})
public getUserById = async ({ path }: ExecuteArgs<{ userId: string }, {}, {}>) => {
const result = await this.userService.info(path?.userId);
return { result };
};
@Post('/', {
title: '사용자 생성',
description: '새로운 사용자를 생성합니다.',
body: CreateUserDto,
response: UserDto,
errors: [UserErrors.EMAIL_DUPLICATE, UserErrors.INVALID_DATA],
})
public createUser = async ({ body, user }: ExecuteArgs<{}, {}, CreateUserDto>) => {
const result = await this.userService.create(body, user);
return { result };
};
}errors 배열에 전달하는 ErrorCreator는 @asapjs/error의 error() 팩토리로 생성합니다:
import { error } from '@asapjs/error';
import { TypeIs } from '@asapjs/schema';
export class UserErrors {
static NOT_FOUND = error(
404,
'USER_NOT_FOUND',
'사용자를 찾을 수 없습니다. ID: {userId}',
{ userId: TypeIs.INT({ comment: '사용자 ID' }) }
);
static EMAIL_DUPLICATE = error(
409,
'USER_EMAIL_DUPLICATE',
'이미 사용 중인 이메일입니다: {email}',
{
email: TypeIs.STRING({ comment: '중복된 이메일' }),
existingUserId: TypeIs.INT({ comment: '기존 사용자 ID' }),
}
);
}errors에 등록된 에러는 Swagger에서 해당 HTTP 상태 코드별 응답 스키마로 자동 문서화됩니다.
라우팅 내부 동작 방식
@Get(path, options)와 같은 데코레이터가 메서드에 적용되면, 데코레이터는 라우트 디스크립터 객체(메서드, 경로, 옵션, 메서드명 문자열)를 클래스 프로토타입의routes배열에 추가합니다.- 생성자에서
registerRoutes()가routes를 순회하며, 이름으로 실제 메서드를 찾아 각 항목에 대해this.excute(method)(options)를 호출합니다. 이때 핸들러는wrapWithEffect()로 감싸져 Effect 기반 트레이싱이 적용됩니다. excute는 다음 순서로expressRouter에 라우트를 등록합니다:jwtVerification(auth)→middleware[]항목들 →Wrapper(handler).- Swagger 경로 및 스키마 정보는 동일한
options객체에서 파생되어 시작 시 전역으로 등록됩니다.errors배열이 있으면 에러 응답 스키마도 함께 등록됩니다.
컨트롤러 등록하기
컨트롤러를 작성한 후, RouterPlugin이 이를 발견하고 마운트할 수 있도록 route.ts 파일에서 컨트롤러 인스턴스를 직접 내보내야 합니다:
// src/route.ts
import UserController from './user/controller/UserController';
import PostController from './post/controller/PostController';
export default [
new UserController(),
new PostController(),
];RouterPlugin은 각 컨트롤러 인스턴스에서 basePath와 expressRouter를 읽어 Express 앱의 /<basePath> 아래에 마운트합니다. .expressRouter만 내보내면 basePath를 읽을 수 없으므로 반드시 인스턴스 자체를 내보내세요.