Skip to Content

ExtendableDto

ExtendableDto는 모든 DTO의 기본 클래스입니다. Sequelize 쿼리 옵션 생성, 데이터 변환, OpenAPI 스키마 생성을 담당합니다.

이 페이지에서 찾을 수 있는 것

심볼타입설명
ExtendableDtoclassDTO 기본 클래스
init()메서드OpenAPI 스키마 등록
map(data)메서드원시 데이터 → DTO 변환
pagingMap(data)메서드페이지네이션 데이터 변환
middleware(as?, user?, extraAs?)메서드Sequelize 쿼리 옵션 생성
generateScheme()메서드OpenAPI 스키마 객체 반환
swagger()메서드$ref 객체 반환

임포트

import { ExtendableDto } from '@asapjs/sequelize';

클래스 개요

class ExtendableDto { static isInitlized: boolean; public init(): void; public map(data: any): any; public pagingMap(data: any): any; public middleware(as?: string, user?: any, extraAs?: string[]): DtoMiddlewareReturn; public generateScheme(): object; public swagger(): object; }

생성자 패턴

ASAPJS DTO는 두 가지 패턴 중 하나를 사용하여 DTO 메타데이터를 등록합니다.

패턴 A — @Dto 클래스 데코레이터 (권장):

@Dto({ defineTable: UsersTable, timestamps: false, name: 'user_dto' }) export default class UserDto extends ExtendableDto { @TypeIs.INT({ comment: '아이디' }) id: number; @TypeIs.STRING({ comment: '이메일' }) email: string; }

@Dto를 사용할 때 init()을 수동으로 호출할 필요가 없습니다 — DTO 파일명이 *Dto.ts이면, initSequelizeModule()이 해당 파일을 스캔하여 자동으로 init()을 호출합니다.

패턴 B — 생성자에서 Reflect.defineMetadata 직접 사용 (레거시):

인스턴스별 제어가 필요한 특수한 경우에 사용할 수 있습니다.


init()

public init(): void

this.constructor.name에서 DTO의 클래스 이름을 읽고 addScheme으로 OpenAPI 스키마를 등록합니다. sequelize::dtoInfo 메타데이터를 설정한 뒤 생성자 내부에서 DTO 클래스당 한 번 호출하세요. 여러 번 호출해도 무해하지만 중복입니다.


map(data)

public map(data: any): any

원시 객체나 Sequelize 모델 인스턴스를 DTO에 선언된 키만 포함하는 일반 객체로 변환합니다.

TypeIs 종류별 동작

필드 종류변환 방식
스칼라 TypeIs (INT, STRING 등)fixValue가 정의된 경우 fixValue(o[key]) 호출; 그렇지 않으면 o[key] 직접 반환
TypeIs.DTO 필드중첩 DTO를 인스턴스화하고 중첩 데이터에 재귀적으로 map()을 호출; 중첩 값이 없으면 null 반환
TypeIs.QUERY 필드QUERY 타입 자체의 fixValue 위임을 통해 fixValue(o[key]) 호출
입력의 알 수 없는 키조용히 제거 — DTO에 선언된 키만 출력

입력 처리

  • data.dataValues 프로퍼티가 있으면(Sequelize 모델 인스턴스) .dataValues 객체를 소스로 사용합니다.
  • data가 배열이면 map()이 각 요소에 적용되어 배열을 반환합니다.
const user = await UsersTable.findOne({ where: { id: 1 } }); const dto = new UserInfoDto().map(user); // dto = { id: 1, email: 'a@example.com', name: 'Alice' } // password 및 목록에 없는 필드는 제외

일반 객체도 전달 가능합니다:

const raw = { id: '5', email: 'b@example.com', name: 'Bob', password: 'secret' }; const dto = new UserInfoDto().map(raw); // dto = { id: 5, email: 'b@example.com', name: 'Bob' } // id는 TypeIs.INT의 fixValue에 의해 문자열 '5'에서 정수 5로 변환 // password는 UserInfoDto에 선언되지 않아 제거

pagingMap(data)

public pagingMap(data: { data: any[]; page: number; page_size: number; [key: string]: any }): any

페이지네이션된 쿼리 결과를 위한 편의 래퍼입니다. data.data의 모든 항목에 map()을 호출하고 결과를 원본 엔벨로프 객체에 다시 합칩니다.

const result = await someService.getPosts(paging); const dto = new PostInfoDto().pagingMap(result); // dto = { data: [...매핑된 DTO...], page: 1, page_size: 10, max_page: 5, ... }

참고: 현재 Swagger 스키마(paging.ts)에서는 has_Next(대문자 N)로 정의되어 있으나, Repository의 실제 반환값은 has_next(소문자 n)입니다. 향후 코드 수정으로 통일될 예정입니다.


middleware(as?, user?, extraAs?)

public middleware( as?: string, user?: any, extraAs?: string[] ): DtoMiddlewareReturn

DTO의 필드 선언에서 파생된 Sequelize 쿼리 옵션을 생성합니다.

반환 타입

interface DtoMiddlewareReturn { as?: string; // 제공된 경우 연관 별칭 model: typeof Model; // defineTable의 엔티티 클래스 attributes: any[]; // Sequelize attributes 배열 (컬럼 + literal 표현식) include?: any[]; // TypeIs.DTO 필드의 중첩 include 옵션 }

필드 처리

필드 종류반환값에 미치는 영향
스칼라 필드 (DTO 또는 QUERY가 아닌)attributes 배열에 키 추가
TypeIs.QUERY 필드[Sequelize.literal(sql), alias] 튜플을 attributes에 추가; query() 함수는 { association, user }를 받음
TypeIs.DTO 필드중첩 DTO에 재귀적으로 middleware()를 호출하고 결과를 include에 추가

파라미터

파라미터타입설명
asstring연관 별칭. 이 DTO가 TypeIs.DTO를 통해 다른 DTO 내부에 중첩될 때 사용됩니다.
userany인증된 사용자 객체(JWT 페이로드에서). TypeIs.QUERYquery() 함수에 전달됩니다.
extraAsstring[]깊이 중첩된 관계를 위한 추가 연관 경로 세그먼트.
const options = new PostInfoDto().middleware(); const posts = await PostsTable.findAll({ ...options, where: { user_id: userId }, });

generateScheme()

public generateScheme(): { type: 'object'; properties: Record<string, any> }

DTO의 모든 TypeIs 데코레이터가 적용된 필드를 순회하며 각각에 toSwagger()를 호출하여 OpenAPI 스키마 객체를 반환합니다.

{ type: 'object', properties: { id: { type: 'integer', format: 'int32', description: 'User ID' }, email: { type: 'string', description: 'Email' }, name: { type: 'string', description: 'Display name' }, } }

swagger()

public swagger(): { $ref: string }

이 DTO의 클래스 이름으로 등록된 스키마 컴포넌트를 가리키는 OpenAPI $ref 객체를 반환합니다.

new UserInfoDto().swagger() // { $ref: '#/components/schemas/UserInfoDto' }

전체 예제

요청 DTO (생성 작업)

// src/user/dto/CreateUserDto.ts import { ExtendableDto, TypeIs } from '@asapjs/sequelize'; export default class CreateUserDto extends ExtendableDto { @TypeIs.STRING({ comment: 'Email address' }) email: string; @TypeIs.PASSWORD({ comment: 'Password' }) password: string; @TypeIs.STRING({ comment: 'Display name' }) name: string; }

@Post 라우트의 body DTO로 사용:

@Post('/register', { title: 'Register', auth: false, body: CreateUserDto, response: UserInfoDto, }) async register({ body }: ExecuteArgs) { return await this.userService.register(body as CreateUserDto); }

응답 DTO (조회 작업)

// src/user/dto/UserInfoDto.ts import { ExtendableDto, TypeIs } from '@asapjs/sequelize'; import UsersTable from '../domain/entity/UsersTable'; export default class UserInfoDto extends ExtendableDto { @TypeIs.INT({ comment: 'User ID' }) id: number; @TypeIs.STRING({ comment: 'Email' }) email: string; @TypeIs.STRING({ comment: 'Display name' }) name: string; }

서비스에서 Sequelize 모델을 변환하는 데 사용:

// src/user/application/UserApplication.ts async getUser(userId: number): Promise<UserInfoDto> { const dto = new UserInfoDto(); const user = await UsersTable.findOne({ ...dto.middleware(), where: { id: userId }, }); return dto.map(user); }

페이지네이션 목록 응답

// src/post/application/PostApplication.ts async getPosts(paging: { page: number; limit: number }) { const dto = new PostInfoDto(); const { count, rows } = await PostsTable.findAndCountAll({ ...dto.middleware(), limit: paging.limit, offset: (paging.page - 1) * paging.limit, order: [['created_at', 'DESC']], }); const max_page = Math.ceil(count / paging.limit); return dto.pagingMap({ data: rows, page: paging.page, page_size: paging.limit, max_page, has_prev: paging.page > 1, has_next: paging.page < max_page, total_elements: count, }); }

라우트는 TypeIs.PAGING으로 엔벨로프 응답을 문서화합니다:

@Get('/', { title: 'List posts', query: PaginationQueryDto, response: TypeIs.PAGING(PostInfoDto), }) async getPosts({ paging }: ExecuteArgs) { return await this.postService.getPosts(paging); }

중첩 DTO (관계형 데이터)

import { ExtendableDto, TypeIs, Dto } from '@asapjs/sequelize'; import PostsTable from '../domain/entity/PostsTable'; import UserInfoDto from '../../user/dto/UserInfoDto'; @Dto({ defineTable: PostsTable }) export default class PostInfoDto extends ExtendableDto { @TypeIs.INT({ comment: 'Post ID' }) id: number; @TypeIs.STRING({ comment: 'Title' }) title: string; @TypeIs.TEXT({ comment: 'Content' }) content: string; @TypeIs.DTO({ dto: UserInfoDto, as: 'user', comment: 'Author' }) user: UserInfoDto; @TypeIs.DATETIME({ comment: 'Created at' }) created_at: Date; }

PostInfoDto에서 middleware()를 호출하면:

{ model: PostsTable, attributes: ['id', 'title', 'content', 'created_at'], include: [ { model: UsersTable, as: 'user', attributes: ['id', 'email', 'name'], } ] }

관련 항목

Last updated on