[NestJS/TypeORM] DataSource 완전 정복 가이드

여리·2025년 5월 29일

TypeORM을 처음 접할 때 헷갈리기 쉬운 DataSource. 단순한 설정을 넘어서 다양한 DB 환경과 구조를 지원하는 핵심 기능이다. 이 글에서는 DataSource의 개념부터 설정, 사용법, 다중 스키마/복제 환경까지 정리해보았다.

NestJS와 Typeorm을 메인으로 사용하고 있는 저에게 필요한 정보들과 공유가 되었으면 한다.

📌 DataSource란?

•	DB와 상호작용하기 위한 설정 객체
•	connection pool 또는 초기 connection을 구축
•	.initialize()로 연결 시작, .destroy()로 연결 종료
•	일반적으로 애플리케이션 부트 시 initialize()만 호출하고 destroy는 하지 않음

⚙️ DataSource 기본 설정

import { DataSource } from "typeorm"

const AppDataSource = new DataSource({
  type: "mysql",
  host: "localhost",
  port: 3306,
  username: "test",
  password: "test",
  database: "test",
})

AppDataSource.initialize()
  .then(() => console.log("Data Source initialized"))
  .catch((err) => console.error("Initialization error", err))

//전역으로 AppDataSource를 export하면 애플리케이션 어디서든 사용 가능해요.

🧩 DB 타입별 분리 예시

const MysqlDataSource = new DataSource({ type: "mysql", ... })
const PostgresDataSource = new DataSource({ type: "postgres", ... })

//포트 번호나 엔진에 따라 설정값이 달라지기 때문에 이렇게 분리해서 관리하는 것도 추천됩니다.

🛠️ DataSource 사용 예시

import { AppDataSource } from "./app-data-source"
import { User } from "../entity/User"

export class UserController {
  @Get("/users")
  getAll() {
    return AppDataSource.manager.find(User)
  }
}

⚙️ DataSourceOptions 요약

{
  host: "localhost", //db 접속정보
  port: 3306, //db 접속정보
  username: "test", //db 접속정보
  password: "test", //db 접속정보
  database: "test", //db 접속정보
  synchronize: true, // 앱 실행 시 자동 동기화
  logging: true, //쿼리 로깅 여부
  entities: ["entity/*.js"], // 엔티티 경로
  migrations: ["migration/*.js"] // 마이그레이션 경로
}

🗂️ 여러 DB / 스키마 / 복제 환경 구성

1. 여러 DB 설정

@Entity({ database: "secondDB" }) // user는 secondDB에 저장
export class User { ... }

@Entity({ database: "thirdDB" }) // photo는 thirdDB에 저장
export class Photo { ... }

2. 여러 스키마 설정

@Entity({ schema: "secondSchema" })
export class User { ... }

@Entity({ schema: "thirdSchema" })
export class Photo { ... }

3. 복제 (Replication)

복제를 설정하면 master-slave 구조로 읽기/쓰기를 분리할 수 있다.

Replication의 경우는 DB에서도 클러스터가 설정(r/w, r/o 구성)이 세팅되어야 복제 기능 제대로 사용할 수 있다.

{
  replication: {
    master: { host: "server1", ... },
    slaves: [
      { host: "server2", ... },
      { host: "server3", ... }
    ]
  },
  canRetry: true,
  selector: "RR" // RR, RANDOM, ORDER 중 선택
}

💡 기억하기:
Master → 쓰기 전용
Slave → 읽기 전용
• 트래픽 분산, 안정성 향상

동작원리

Manager 사용

const users = await dataSource.manager.find(User)

Repository 사용

const repo = dataSource.getRepository(User)
const users = await repo.find()

🔧 DataSource API 총정리

// 상태 확인
dataSource.isInitialized //dataSource, 초기 연결 초기화 되었는지
dataSource.driver //dataSource의 db driver 설정

// 엔티티 매니저 사용 예시
const users = await dataSource.manager.find(User)

// 초기화, 종료, 동기화 등
await dataSource.initialize() //data source 초기화, db에 연결 pool 오픈
await dataSource.destroy() //data source 제거 및 모든 db 연결 해제
await dataSource.synchronize() //db schema 동시성
await dataSource.dropDatabase() //db drop후 모든 데이터 삭제
await dataSource.runMigrations() //migration 실행

// 레포지토리 사용
const repo = dataSource.getRepository(User)
const users = await repo.find()

✅ 트랜잭션 실행

// 방법 1(dataSource.transaction() 방식)
await dataSource.transaction(async (manager) => {
  await manager.save(...)
  await manager.update(...)
})

//📌 특징
//	•	간편하고 선언적인 트랜잭션 처리
//	•	내부적으로 QueryRunner를 생성, 시작, 커밋, 해제까지 자동으로 처리
//	•	NestJS 공식 문서에서도 일반적인 트랜잭션 처리에 가장 권장되는 방식

//✅ 장점
//	•	코드가 간결하고 실수 방지에 좋음
//	•	자동으로 rollback/commit 처리
//	•	entityManager만으로 대부분의 CRUD 처리 가능

//⚠️ 단점
//	•	복잡한 쿼리 흐름, 수동 컨트롤이 필요한 경우에는 제한적
//	•	entityManager.query() 외의 메서드는 사용 제한이 있을 수 있음

// 방법2 (QueryRunner 방식)
const queryRunner = dataSource.createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction()

try {
  await queryRunner.manager.save(...)
  await queryRunner.manager.update(...)
  await queryRunner.commitTransaction()
} catch (err) {
  await queryRunner.rollbackTransaction()
} finally {
  await queryRunner.release()
}
//📌 특징
//	•	수동 트랜잭션 관리 방식
//	•	더 정교한 흐름 제어 가능 (중간에 조건 분기, 세밀한 커밋 타이밍 조절 등)
//	•	여러 DB 조작이 혼합된 복잡한 시나리오에서 유리

//✅ 장점
//	•	유연함: 트랜잭션 범위를 더 정교하게 컨트롤 가능
//	•	queryRunner.query()로 raw query도 처리 가능
//	•	세션 유지 등도 직접 제어 가능

//⚠️ 단점
//	•	코드가 길고 복잡
//	•	connect, release 같은 리소스 관리 누락 시 메모리 누수 가능성 있음
//	•	실수하기 쉽고 boilerplate 코드가 많음

🏆 NestJS에서는 어떤 방식이 권장될까?
NestJS + TypeORM 공식 가이드에서 기본적으로는 dataSource.transaction() 방식을 권장합니다.
• 이유는 코드 가독성, 오류율 감소, 간단한 CRUD에 충분한 기능 제공
• 다만, 아래와 같은 경우에는 QueryRunner 방식이 필요합니다:
• 하나의 트랜잭션 내에서 raw SQL을 실행해야 하는 경우
• 트랜잭션 중간에 조건 분기하여 커밋 시점을 수동으로 제어해야 할 경우
• 트랜잭션 범위를 코드 레벨에서 더 유연하게 조작해야 하는 경우

✅ Raw Query 실행

const rawData = await dataSource.query('SELECT * FROM USERS')

✅ QueryBuilder 사용

const users = await dataSource
  .createQueryBuilder()
  .select()
  .from(User, "user")
  .where("user.name = :name", { name: "John" })
  .getMany()

✅ QueryRunner로 커넥션 직접 다루기

const queryRunner = dataSource.createQueryRunner()
await queryRunner.connect()

// 쿼리 실행 등 작업

await queryRunner.release()

아래 원글과 참고글을 통해서 보면 더욱 도움이 될 것이다.

원글: https://dream-and-develop.tistory.com/269
참고글

profile
beckend developer

0개의 댓글