Skip to Content
문서가이드데이터베이스 관리

데이터베이스 관리

이 가이드에서는 ASAPJS에서 데이터베이스를 설정하고, 엔티티를 정의하며, 쿼리를 수행하는 전체 흐름을 다룹니다. @asapjs/sequelize 패키지는 Sequelize ORM 위에 데코레이터 기반의 추상화를 제공하여, 모델 정의와 Swagger 스키마 생성을 하나의 코드로 처리합니다.

[!TIP] API 레퍼런스는 Database 페이지를 참고하세요. 이 가이드는 실제 프로젝트에서의 활용 패턴에 초점을 맞추고 있습니다.

데이터베이스 연결 설정

기본 설정

Application 생성 시 config 객체에 extensionsdb 블록을 포함합니다.

// src/index.ts // 소스: example/src/index.ts import 'reflect-metadata'; import dotenv from 'dotenv'; import { Application } from '@asapjs/core'; dotenv.config(); const config = { name: 'My App', port: process.env.PORT || 3000, basePath: 'api', extensions: ['@asapjs/router', '@asapjs/sequelize'], // Router + Sequelize 확장 활성화 auth: { jwt_access_token_secret: process.env.JWT_SECRET || 'secret', }, db: { username: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'myapp', host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '3306', 10), dialect: 'mysql', logging: false, }, }; const app = new Application(__dirname, config); app.run();

환경 변수

.env 파일로 데이터베이스 연결 정보를 관리합니다:

DB_USER=root DB_PASSWORD=password DB_NAME=myapp DB_HOST=localhost DB_PORT=3306 DB_SYNC=true

자동 데이터베이스 생성

initSequelizeModule()은 연결 시 데이터베이스가 존재하지 않으면 자동으로 생성을 시도합니다. MySQL의 ER_BAD_DB_ERROR 에러를 감지하면, 데이터베이스 이름 없이 임시 연결을 열고 CREATE DATABASE IF NOT EXISTS를 실행한 후 재연결합니다.

// 내부 동작 흐름 (packages/sequelize/src/sequelize/index.ts) 1. dbInit(config) → Sequelize 인스턴스 생성 2. authenticate() 시도 3. ER_BAD_DB_ERROR 발생 시: → 임시 연결 (database: undefined) → CREATE DATABASE IF NOT EXISTS → 원래 연결로 재인증

[!NOTE] 자동 데이터베이스 생성은 MySQL에서만 동작합니다. PostgreSQL이나 SQLite를 사용할 경우 데이터베이스를 미리 생성해야 합니다.

초기화 흐름

Application.run()이 호출되면 다음 순서로 데이터베이스가 초기화됩니다:

Application.run() └─ initMoudles() └─ initSequelizeModule(dirname) ├─ dbInit(config.db) // DB 연결 ├─ loadPath(dirname, '') // *Table.js, *Dto.js 파일 스캔 ├─ addModelsToSequelize() // 모델 등록 └─ addDtosToSwagger() // DTO → Swagger 스키마 등록

loadPath()는 컴파일된 출력 디렉토리(dirname)를 재귀적으로 탐색하여:

  • *Table.js 파일 → Sequelize 모델로 등록
  • *Dto.js 파일 → Swagger 컴포넌트 스키마로 등록

.map 파일은 자동으로 무시됩니다.

엔티티(모델) 정의

@Table 데코레이터

@Table 데코레이터와 TypeIs.* 데코레이터를 조합하여 Sequelize 모델을 정의합니다. 하나의 코드에서 DB 컬럼 메타데이터와 Swagger 스키마가 동시에 생성됩니다.

// src/user/domain/entity/UsersTable.ts // 소스: example/src/user/domain/entity/UsersTable.ts import { Model } from 'sequelize-typescript'; import { Table, TypeIs } from '@asapjs/sequelize'; @Table({ tableName: 'users', timestamps: true, }) export default class UsersTable extends Model { @TypeIs.INT({ primaryKey: true, autoIncrement: true, comment: '사용자 ID' }) id: number; @TypeIs.STRING({ unique: true, comment: '이메일 (고유)' }) email: string; @TypeIs.PASSWORD({ comment: '비밀번호 (bcrypt)' }) password: string; @TypeIs.STRING({ comment: '사용자 이름' }) name: string; @TypeIs.DATETIME({ comment: '생성 일시' }) created_at: Date; @TypeIs.DATETIME({ comment: '수정 일시' }) updated_at: Date; }

@Table 옵션:

옵션타입설명
tableNamestring실제 DB 테이블 이름
timestampsbooleantrue이면 created_at, updated_at 자동 매핑

@Table은 내부적으로 charset을 utf8mb4, collation을 utf8mb4_general_ci로 설정합니다.

연관 관계 (Foreign Key & BelongsTo)

테이블 간 관계는 TypeIs.FOREIGNKEYTypeIs.BELONGSTO로 정의합니다:

// src/post/domain/entity/PostsTable.ts // 소스: example/src/post/domain/entity/PostsTable.ts import { Model } from 'sequelize-typescript'; import { Table, TypeIs } from '@asapjs/sequelize'; import UsersTable from '../../../user/domain/entity/UsersTable'; @Table({ tableName: 'posts', timestamps: true, }) export default class PostsTable extends Model { @TypeIs.INT({ primaryKey: true, autoIncrement: true, comment: '게시글 ID' }) id: number; @TypeIs.STRING({ comment: '게시글 제목' }) title: string; @TypeIs.TEXT({ comment: '게시글 내용' }) content: string; @TypeIs.FOREIGNKEY({ table: () => UsersTable, comment: '작성자 ID' }) user_id: number; @TypeIs.BELONGSTO(() => UsersTable, 'user_id') user: UsersTable; @TypeIs.DATETIME({ comment: '생성 일시' }) created_at: Date; @TypeIs.DATETIME({ comment: '수정 일시' }) updated_at: Date; }
  • FOREIGNKEY: 외래 키 컬럼을 정의합니다. table 옵션에 참조 대상 모델을 지정합니다 (순환 참조 방지를 위해 화살표 함수 사용).
  • BELONGSTO: Sequelize의 BelongsTo 연관 관계를 설정합니다. 두 번째 인자는 외래 키 컬럼 이름입니다.

TypeIs 타입 레퍼런스

TypeIs는 각 데코레이터마다 Sequelize 컬럼 타입과 Swagger 스키마를 동시에 생성합니다:

데코레이터Sequelize 타입Swagger 타입설명
TypeIs.INTINTEGERinteger (int32)32비트 정수
TypeIs.BIGINTBIGINTinteger (int64)64비트 정수
TypeIs.FLOATFLOATnumber (float)부동소수점
TypeIs.DOUBLEDOUBLEnumber (double)배정밀도 부동소수점
TypeIs.DECIMALDECIMALnumber정밀 소수
TypeIs.STRINGSTRINGstring가변 길이 문자열
TypeIs.TEXTTEXTstring대용량 텍스트
TypeIs.PASSWORDSTRING(512)string (password)비밀번호 (format: password)
TypeIs.BOOLEANBOOLEANboolean불리언
TypeIs.DATEONLYDATEONLYstring (date)날짜만
TypeIs.DATETIMEDATEstring (date-time)날짜+시간
TypeIs.ENUMENUMstring (enum)열거형
TypeIs.JSONJSONobjectJSON 데이터
TypeIs.FOREIGNKEYINTEGER (기본)외래 키
TypeIs.BELONGSTOBelongsTo 연관

모든 타입 데코레이터는 comment 옵션을 지원하며, 이 값이 Swagger의 description 필드로 매핑됩니다.

DTO (Data Transfer Object)

DTO는 API 요청/응답의 형태를 정의하며, ExtendableDto를 상속합니다. 모델과 동일한 TypeIs.* 데코레이터를 사용합니다.

// src/user/dto/UserInfoDto.ts import { ExtendableDto, Dto, TypeIs } from '@asapjs/sequelize'; import UsersTable from '../domain/entity/UsersTable'; @Dto({ name: 'user_info_dto', defineTable: UsersTable }) export default class UserInfoDto extends ExtendableDto { @TypeIs.INT({ comment: '사용자 ID' }) id: number; @TypeIs.STRING({ comment: '이메일' }) email: string; @TypeIs.STRING({ comment: '이름' }) name: string; @TypeIs.DATETIME({ comment: '가입일' }) created_at: Date; }

DTO의 역할:

  • Swagger 스키마 생성: init() 메서드가 호출되면 TypeIs 메타데이터에서 JSON Schema를 자동 생성
  • 필드 필터링: middleware() 메서드가 Sequelize 쿼리의 attributes 목록을 DTO 필드로 제한
  • 데이터 변환: map() 메서드가 Sequelize 모델 인스턴스를 DTO 형태로 변환

Repository 패턴

Application 계층에서 Repository를 상속하면 this.repository.findAllthis.repository.findOne을 사용할 수 있습니다.

// src/user/application/UserApplication.ts import { Repository } from '@asapjs/sequelize'; import UsersTable from '../domain/entity/UsersTable'; import UserInfoDto from '../dto/UserInfoDto'; export default class UserApplication extends Repository { async getUsers(paging: { page: number; limit: number }, user: any) { return await this.repository.findAll(UsersTable, { exportTo: UserInfoDto, user, paging, order: [['created_at', 'DESC']], }); } async getUserById(id: number, user: any) { return await this.repository.findOne(UsersTable, { exportTo: UserInfoDto, user, where: { id }, }); } }

findAll — 페이지네이션 지원

paging 옵션을 전달하면 자동으로 findAndCountAll을 실행하고 페이지네이션 응답을 반환합니다:

// paging 포함 시 응답 형태 { data: T[], // 현재 페이지의 레코드 배열 page: number, // 현재 페이지 (0-based) page_size: number, // 페이지당 항목 수 max_page: number, // 마지막 페이지 인덱스 has_prev: boolean, // 이전 페이지 존재 여부 has_next: boolean, // 다음 페이지 존재 여부 total_elements: number // 전체 레코드 수 }

paging 없이 호출하면 일반 배열을 반환합니다.

스키마 동기화

modelsSync()

modelsSync()는 등록된 모든 Sequelize 모델의 정의를 데이터베이스 스키마와 동기화합니다:

// src/index.ts import { Application } from '@asapjs/core'; import { modelsSync } from '@asapjs/sequelize'; const app = new Application(__dirname, config); app.run(async () => { if (process.env.DB_SYNC === 'true') { await modelsSync(); } });

내부적으로 sync({ alter: { drop: false } })를 사용하므로:

  • 누락된 컬럼을 추가합니다
  • 컬럼 타입을 변경합니다
  • 기존 컬럼이나 테이블을 절대 삭제하지 않습니다

[!WARNING] modelsSync()는 개발 환경에서 스키마를 빠르게 반영할 때 유용합니다. 프로덕션 환경에서는 마이그레이션 도구 사용을 권장합니다.

편의 스크립트

# 스키마 동기화 cd example && yarn db:sync # DB_SYNC=true로 실행 # 시드 데이터 삽입 cd example && yarn db:seed # 동기화 + 시드 cd example && yarn db:reset

헬스 체크

healthCheck()SELECT 1 쿼리를 1초 타임아웃으로 실행하여 데이터베이스 연결 상태를 확인합니다:

// src/health/controller/HealthController.ts import { RouterController, Get, ExecuteArgs } from '@asapjs/router'; import { healthCheck } from '@asapjs/sequelize'; export default class HealthController extends RouterController { public basePath = '/health'; public tag = 'Health'; constructor() { super(); this.registerRoutes(); } @Get('/', { title: 'Health check', auth: false }) async check({}: ExecuteArgs) { await healthCheck(); return { status: 'ok' }; } }

Kubernetes liveness/readiness probe나 로드 밸런서의 헬스 체크 엔드포인트로 활용할 수 있습니다.

직접 Sequelize 접근

getSequelize()로 글로벌 Sequelize 인스턴스에 접근하여 트랜잭션, raw 쿼리 등을 직접 수행할 수 있습니다:

import { getSequelize } from '@asapjs/sequelize'; // 트랜잭션 사용 const sequelize = getSequelize(); await sequelize.transaction(async (t) => { await UsersTable.create({ name: 'Alice' }, { transaction: t }); await PostsTable.create({ title: 'Hello', user_id: 1 }, { transaction: t }); });

파일 명명 규칙 요약

파일 패턴역할등록 대상
*Table.tsSequelize 모델addModels() → DB 테이블
*Dto.tsDTO 클래스dto.init() → Swagger 스키마

파일은 dirname 하위 어떤 깊이에도 위치할 수 있으며, 재귀적으로 탐색됩니다.

관련 문서

Last updated on