Skip to Content
API 레퍼런스요청 처리

요청 처리

ASAPJS의 모든 라우트 핸들러는 단일 ExecuteArgs 인수를 받습니다. Wrapper 유틸리티(@asapjs/router 내부)는 Express의 원시 RequestResponse 객체에서 관련 필드를 추출하여, 정규화되고 타입이 지정된 형태로 핸들러에 전달합니다.

ExecuteArgs 인터페이스

import { ExecuteArgs } from '@asapjs/router';

정의

export interface ExecuteArgs<P = {}, Q = {}, B = {}> { req: Request; res: Response; path?: P | { [key: string]: any }; query: Q & { [key: string]: any }; body: B & { [key: string]: any }; files?: { [key: string]: any }; user?: any; paging: PaginationQueryType; }

세 가지 제네릭 타입 파라미터는 선택 사항이며, path, query, body의 추론 타입을 각각 좁히는 데 사용할 수 있습니다.

필드 레퍼런스

필드타입출처설명
reqRequestExpress 요청 객체원시 Express Request. 다른 필드에서 다루지 않는 항목(헤더, 쿠키, IP 등)에 이 필드를 사용합니다.
resResponseExpress 응답 객체원시 Express Response. 커스텀 응답(예: 파일 스트림)을 직접 전송해야 할 때만 사용합니다. 일반적인 방법은 핸들러에서 값을 반환하는 것입니다.
pathP | { [key: string]: any }req.paramsExpress가 파싱한 URL 경로 파라미터. 키는 라우트 경로 문자열의 :name 세그먼트와 일치합니다. 모든 값은 문자열로 전달되며, 필요한 경우 수동으로 파싱해야 합니다.
queryQ & { [key: string]: any }req.query파싱된 쿼리 스트링 객체. DTO 레이어에서 변환하지 않는 한 모든 값은 문자열 또는 문자열 배열입니다. pagelimit 키는 별도로 소비되어 paging으로 들어갑니다.
bodyB & { [key: string]: any }req.body파싱된 요청 바디. application/json 요청의 경우 JSON 디코딩된 객체입니다. multipart/form-data 요청의 경우 바이너리 필드는 files를 사용합니다.
files{ [key: string]: any }req.filesmultipart/form-data 요청의 파일 업로드. 라우트의 middleware 배열에 파일 업로드 미들웨어(예: multer)가 설정된 경우에만 존재합니다.
useranyreq.user유효한 Bearer 토큰이 있을 때 jwtVerification이 설정하는 디코딩된 JWT 페이로드. 토큰이 제공되지 않은 라우트에서는 undefined. 인증을 참고하세요.
pagingPaginationQueryTypereq.query.page, req.query.limit미리 파싱된 페이지네이션 plain object { page, limit }. 항상 존재하며, 쿼리 파라미터가 없을 때 기본값은 page: 0, limit: 20입니다.

Wrapper가 ExecuteArgs를 구성하는 방법

Wrapper 함수는 등록된 모든 라우트 핸들러에 적용됩니다. 주요 역할은 다음과 같습니다:

  1. req.query.page(기본값 0)와 req.query.limit(기본값 20)를 읽어 정수로 파싱하고 PaginationQueryType plain object를 생성합니다.
  2. req.user, req.query, req.params, req.body, paging 값으로 ExecuteArgs 객체를 조립합니다.
  3. ExecuteArgs를 인수로 핸들러를 await합니다.
  4. 핸들러가 truthy 값을 반환하면 res.status(200).json(output)으로 응답합니다.
  5. 던져진 오류를 잡아 errorToResponse()로 위임합니다 (아래 오류 처리 섹션 참고).
// packages/router/src/utils/wrapper.ts — 실제 구현 import type { PaginationQueryType } from '@asapjs/sequelize'; import { errorToResponse } from '@asapjs/error'; export interface ExecuteArgs<P = {}, Q = {}, B = {}> { req: Request; res: Response; path?: P | { [key: string]: any }; query: Q & { [key: string]: any }; body: B & { [key: string]: any }; files?: { [key: string]: any }; user?: any; paging: PaginationQueryType; } export default function Wrapper(cb: (args: ExecuteArgs) => Promise<unknown>) { return async function _Wrapper(req: Request, res: Response, next: NextFunction) { try { const user = req.user; const { page: pageProp = 0, limit: limitProp = 20 } = req.query; const page = parseInt(String(pageProp), 10); const limit = parseInt(String(limitProp), 10); const paging: PaginationQueryType = { page, limit }; const args: ExecuteArgs = { req, res, query: req.query, path: req.params, body: req.body, user, paging, }; const output = await cb(args); if (output) { res.status(200).json(output); } } catch (err) { // 서버 에러(500 또는 status 미지정) → 로깅 + Sentry 캡처 // 모든 에러 → errorToResponse(err, res)로 위임 errorToResponse(err, res); } }; }

PaginationQueryType과 PaginationQueryDto

Wrapper는 모든 요청에서 req.query.pagereq.query.limit을 파싱하여 PaginationQueryType plain object를 생성하고 ExecuteArgs.paging으로 제공합니다.

import { PaginationQueryType, PaginationQueryDto } from '@asapjs/sequelize';

PaginationQueryType

PaginationQueryTypePaginationQueryDto에서 pagelimit만 추출한 타입 별칭입니다. ExecuteArgs.paging의 실제 타입입니다.

type PaginationQueryType = Pick<PaginationQueryDto, 'page' | 'limit'>; // 결과: { page: number; limit: number }
필드타입쿼리 파라미터기본값설명
pagenumber?page=00 기반 페이지 인덱스.
limitnumber?limit=20페이지당 레코드 수.

PaginationQueryDto

PaginationQueryDto@asapjs/sequelize의 DTO 클래스로, Swagger 쿼리 파라미터 문서화에 사용합니다. 데코레이터의 query 옵션에 전달하면 Swagger가 pagelimit를 쿼리 파라미터로 문서화합니다.

핸들러에서 paging 사용하기

@Get('/', { title: 'List posts', query: PaginationQueryDto, response: TypeIs.PAGING(PostInfoDto), }) async getPosts({ paging }: ExecuteArgs) { // paging.page — ?page=에서 파싱된 정수 // paging.limit — ?limit=에서 파싱된 정수 return await this.postService.getPosts(paging); }

query 옵션에 PaginationQueryDto를 전달하는 것은 Swagger 문서화를 위한 것입니다. paging 필드는 query 설정 여부와 관계없이 Wrapper가 항상 채워줍니다.

응답 반환하기

응답을 전송하는 일반적인 방법은 핸들러에서 값을 반환하는 것입니다:

async getPost({ path }: ExecuteArgs) { const postId = parseInt((path as any)?.postId as string, 10); return await this.postService.getPost(postId); // HTTP 200 JSON으로 변환 }

Wrapper는 단순 truthy 검사(if (output))로 반환 값을 확인합니다. 따라서:

  • 객체, 배열, 또는 truthy 값을 반환하면 → HTTP 200 application/json.
  • undefined, null, 또는 0을 반환하면 → 자동으로 응답이 전송되지 않으므로, res 필드를 통해 res.json() 또는 res.end()를 직접 호출해야 합니다.
  • { success: true }를 반환하면 → 반환할 의미 있는 바디가 없는 삭제 작업에서 자주 사용하는 패턴입니다.
async deletePost({ path, user }: ExecuteArgs) { await this.postService.deletePost(postId, user); return { success: true }; // HTTP 200 { "success": true } }

오류 처리

ASAPJS에는 두 가지 에러 처리 경로가 있습니다:

경로 1: Wrapper 내부 — errorToResponse() (@asapjs/error)

라우트 핸들러에서 던져진 에러는 Wrapper의 try/catch에서 잡혀 @asapjs/errorerrorToResponse()로 위임됩니다. 이 경로는 @asapjs/errorerror() 팩토리로 생성한 HttpError와 legacy HttpException을 구분하여 처리합니다.

에러 유형조건HTTP 상태바디
HttpError (@asapjs/error)error() 팩토리로 생성err.status{ status, errorCode, message, data }
HttpException (legacy)statusmessage만 있고 errorCode 없음err.status{ status, errorCode: 'LEGACY_HTTP_EXCEPTION', message }
처리되지 않은 오류status 속성 없음500{ status: 500, errorCode: 'INTERNAL_SERVER_ERROR', message }

권장: 새 코드에서는 @asapjs/errorerror() 팩토리를 사용하세요. errorCodedata를 포함하는 구조화된 에러 응답을 제공합니다.

import { error } from '@asapjs/error'; import { TypeIs } from '@asapjs/schema'; const PostNotFound = error(404, 'POST_NOT_FOUND', '게시글을 찾을 수 없습니다', { postId: TypeIs.INT({ comment: '게시글 ID' }), }); // 핸들러에서 사용: async getPost({ path }: ExecuteArgs) { const postId = parseInt((path as any)?.postId, 10); const post = await this.postService.getPost(postId); if (!post) { throw PostNotFound({ postId }); // → HTTP 404 { status: 404, errorCode: 'POST_NOT_FOUND', message: '게시글을 찾을 수 없습니다', data: { postId: 42 } } } return post; }

경로 2: Express 전역 에러 핸들러 — errorHandler (@asapjs/router)

Wrapper를 거치지 않는 에러(예: 미들웨어에서 next(error) 호출)는 Express 미들웨어 체인 끝에 등록된 errorHandler가 처리합니다. 이 핸들러는 간단한 2-필드 응답을 반환합니다.

에러 유형HTTP 상태바디
HttpExceptionerr.status{ status, message }
일반 에러500{ status: 500, message }
import { HttpException } from '@asapjs/router'; // HttpException은 간단한 HTTP 에러에 적합합니다: throw new HttpException(404, 'Post not found'); // → HTTP 404 { status: 404, message: 'Post not found' }

HttpException 생성자 시그니처는 인증을 참고하세요.

requestType과 responseType 헬퍼

이 헬퍼 팩토리들은 배열이나 중첩 DTO 구조를 나타내고자 할 때 bodyresponse 옵션에 사용할 DtoOrTypeIs 값을 생성합니다.

import { requestType, responseType } from '@asapjs/router';

시그니처

const requestType: ( Dto: Constructor | true, innerType?: 'body' | 'query', isArray?: boolean ) => () => { type: 'body' | 'query'; data: any } | undefined | true const responseType: ( Dto: Constructor | true, innerType?: 'body' | 'query', isArray?: boolean ) => () => { type: 'body' | 'query'; data: any } | undefined | true
파라미터타입기본값설명
DtoConstructor | true래핑할 DTO 클래스. 타입은 존재하지만 스키마가 없음을 나타내려면 true를 전달합니다.
innerType'body' | 'query''body'DTO가 body 컨텍스트를 설명하는지 query 컨텍스트를 설명하는지 지정합니다.
isArraybooleanfalsetrue이면 Swagger용 { type: 'array', items: ... } 구조로 DTO 스키마를 감쌉니다.

사용 예제

import { Post, requestType, responseType } from '@asapjs/router'; import UserInfoDto from '../dto/UserInfoDto'; @Post('/batch', { title: 'Batch create users', body: requestType(CreateUserDto, 'body', true), // body에 CreateUserDto 배열 response: responseType(UserInfoDto, 'body', true), // 응답에 UserInfoDto 배열 }) async batchCreate({ body }: ExecuteArgs) { return await this.userService.batchCreate(body as CreateUserDto[]); }

페이지네이션 응답에는 requestType/responseType 대신 @asapjs/sequelizeTypeIs.PAGING(Dto)를 사용하세요. 이 메서드가 Swagger UI에 맞는 올바른 엔벨로프 스키마를 생성합니다.

원시 요청 데이터 접근하기

구조화된 필드로 충분하지 않을 때는 reqres를 직접 사용합니다:

@Get('/download/:fileId', { title: 'Download file', auth: true, }) async downloadFile({ req, res, path }: ExecuteArgs) { const fileId = (path as any)?.fileId as string; const stream = await this.fileService.getStream(fileId); // 커스텀 헤더 설정 및 파이프 — 값을 반환하지 않음 res.setHeader('Content-Disposition', `attachment; filename="${fileId}"`); stream.pipe(res); // undefined를 반환하면 Wrapper가 res.json()을 호출하지 않습니다 }

관련 항목

  • 라우팅 — HTTP 메서드 데코레이터와 IOptions
  • 인증 — JWT 검증과 user 필드
Last updated on