Skip to Content

라우팅

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
파라미터타입설명
pathstring컨트롤러의 basePath에 대한 상대 URL 경로 세그먼트. Express 경로 파라미터를 지원합니다 (예: '/:id'). basePath 자체와 매칭하려면 '/'를 사용합니다.
optionsIOptionsSwagger 생성, 인증, body/query 형태, 라우트별 미들웨어를 위한 라우트 설정 옵션.

IOptions 인터페이스

IOptionsIPreference에서 내부 필드인 pathexcute를 제거한 형태입니다. 모든 필드는 선택 사항입니다.

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[]; }
필드타입기본값설명
titlestring''UI에 표시되는 Swagger 오퍼레이션 요약 레이블.
descriptionstring''Swagger 오퍼레이션 상세 화면에 렌더링되는 긴 설명.
summarystringSwagger summary 필드의 별칭.
deprecatedbooleanfalsetrue이면 Swagger UI에서 해당 오퍼레이션을 deprecated로 표시합니다.
authbooleanfalsetrue이면 jwtVerification 미들웨어가 유효한 Bearer 토큰을 요구합니다. 인증을 참고하세요.
bodyDtoOrTypeIs요청 바디를 설명하는 DTO 클래스 또는 TypeIs.* 표현식. Swagger requestBody 스키마 생성에 사용됩니다.
bodyContentType'application/json' | 'multipart/form-data''application/json'요청 바디의 Content-Type. 파일 업로드 시 'multipart/form-data'로 설정합니다.
queryDtoOrTypeIs쿼리 스트링 파라미터를 설명하는 DTO 클래스. Swagger 쿼리 파라미터 항목 생성에 사용됩니다.
responseDtoOrTypeIsHTTP 200 응답 형태를 설명하는 DTO 클래스 또는 TypeIs.* 표현식.
errorsErrorCreator[]이 라우트에서 발생할 수 있는 에러 목록. @asapjs/errorerror() 팩토리로 생성합니다. Swagger에 에러 응답 스키마(status, errorCode, message, data)를 자동 등록합니다.
middlewareany[][]이 라우트에만 적용되는 추가 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; }
멤버타입설명
basePathstring이 컨트롤러의 모든 라우트에 대한 URL 접두사. 서브클래스에서 재정의합니다 (예: '/users'). 기본값은 '/'.
tagstring이 컨트롤러의 모든 라우트를 그룹화하는 Swagger 태그. 서브클래스에서 재정의합니다 (예: 'User').
expressRouterRouterExpress 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/errorerror() 팩토리로 생성합니다:

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 상태 코드별 응답 스키마로 자동 문서화됩니다.

라우팅 내부 동작 방식

  1. @Get(path, options)와 같은 데코레이터가 메서드에 적용되면, 데코레이터는 라우트 디스크립터 객체(메서드, 경로, 옵션, 메서드명 문자열)를 클래스 프로토타입의 routes 배열에 추가합니다.
  2. 생성자에서 registerRoutes()routes를 순회하며, 이름으로 실제 메서드를 찾아 각 항목에 대해 this.excute(method)(options)를 호출합니다. 이때 핸들러는 wrapWithEffect()로 감싸져 Effect 기반 트레이싱이 적용됩니다.
  3. excute는 다음 순서로 expressRouter에 라우트를 등록합니다: jwtVerification(auth)middleware[] 항목들 → Wrapper(handler).
  4. 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은 각 컨트롤러 인스턴스에서 basePathexpressRouter를 읽어 Express 앱의 /<basePath> 아래에 마운트합니다. .expressRouter만 내보내면 basePath를 읽을 수 없으므로 반드시 인스턴스 자체를 내보내세요.

관련 항목

  • 인증 — JWT 검증 및 auth 옵션 동작
  • 요청 처리ExecuteArgs, paging, 응답 포맷
Last updated on