데이터베이스 관리
이 가이드에서는 ASAPJS에서 데이터베이스를 설정하고, 엔티티를 정의하며, 쿼리를 수행하는 전체 흐름을 다룹니다. @asapjs/sequelize 패키지는 Sequelize ORM 위에 데코레이터 기반의 추상화를 제공하여, 모델 정의와 Swagger 스키마 생성을 하나의 코드로 처리합니다.
[!TIP] API 레퍼런스는 Database 페이지를 참고하세요. 이 가이드는 실제 프로젝트에서의 활용 패턴에 초점을 맞추고 있습니다.
데이터베이스 연결 설정
기본 설정
Application 생성 시 config 객체에 extensions와 db 블록을 포함합니다.
// 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 옵션:
| 옵션 | 타입 | 설명 |
|---|---|---|
tableName | string | 실제 DB 테이블 이름 |
timestamps | boolean | true이면 created_at, updated_at 자동 매핑 |
@Table은 내부적으로 charset을 utf8mb4, collation을 utf8mb4_general_ci로 설정합니다.
연관 관계 (Foreign Key & BelongsTo)
테이블 간 관계는 TypeIs.FOREIGNKEY와 TypeIs.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.INT | INTEGER | integer (int32) | 32비트 정수 |
TypeIs.BIGINT | BIGINT | integer (int64) | 64비트 정수 |
TypeIs.FLOAT | FLOAT | number (float) | 부동소수점 |
TypeIs.DOUBLE | DOUBLE | number (double) | 배정밀도 부동소수점 |
TypeIs.DECIMAL | DECIMAL | number | 정밀 소수 |
TypeIs.STRING | STRING | string | 가변 길이 문자열 |
TypeIs.TEXT | TEXT | string | 대용량 텍스트 |
TypeIs.PASSWORD | STRING(512) | string (password) | 비밀번호 (format: password) |
TypeIs.BOOLEAN | BOOLEAN | boolean | 불리언 |
TypeIs.DATEONLY | DATEONLY | string (date) | 날짜만 |
TypeIs.DATETIME | DATE | string (date-time) | 날짜+시간 |
TypeIs.ENUM | ENUM | string (enum) | 열거형 |
TypeIs.JSON | JSON | object | JSON 데이터 |
TypeIs.FOREIGNKEY | INTEGER (기본) | — | 외래 키 |
TypeIs.BELONGSTO | — | — | BelongsTo 연관 |
모든 타입 데코레이터는 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.findAll과 this.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.ts | Sequelize 모델 | addModels() → DB 테이블 |
*Dto.ts | DTO 클래스 | dto.init() → Swagger 스키마 |
파일은 dirname 하위 어떤 깊이에도 위치할 수 있으며, 재귀적으로 탐색됩니다.
관련 문서
- Database API 레퍼런스 —
initSequelizeModule,getSequelize,Repository상세 API - 타입 시스템 — TypeIs 데코레이터 심층 가이드
- Bootstrap —
Application클래스,IConfig설정 레퍼런스