TypeOrmModule
TypeORM은 Node.js에서 가장 널리 쓰이는 ORM 중 하나이다. 엔티티(클래스) 와 테이블(DDL) 매핑, 관계(연관 관계)관리, 리포지토리 패턴, 트랜잭션, 마이그레이션 까지 제공해준다.
@nestjs/typeorm (TypeOrmModule) : NestJS DI/모듈 시스템에 TypeORM을 자연스럽게 사용할 수 있게 해주는 어댑터 모듈이다.
- 커넥션(DataSource) 생성/주기 관리
- 모듈 단위로 *Repository 주입 (@InjectRepository) 가능하게 함
- 환경 설정 (동기/비동기)과 멀티 커넥션 지원
예시를 통해 알아보기
// app.module.ts
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: process.env.MYSQL_HOST,
port: parseInt(process.env.MYSQL_PORT || '3306', 10),
username: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DB,
entities: [
Media,
MediaCore,
MediaReaction,
Comment,
CommentReaction,
User,
],
autoLoadEntities: false,
synchronize: false,
logging: true,
}),
}),
TypeOrmModule.forRootAsync(...) : 루트 커넥션 설정
- 애플리케이션 전체에서 사용할 DB 커넥션을 설정
- 환경 변수, ConfigService 등 비동기 설정이 필요할 때 사용한다.
- 중요 필드
- entities : 커넥션에 메타데이터로 올릴 모든 엔티티 목록. (여기에 빠진 엔티티는 관계 인식이 안된다.)
- autoLoadEntities : true 면 각 모듈의 forFeature에 등장하는 엔티티를 자동으로 등록한다.
- synchronize : 스키마 자동 동기화 (true는 개발용).
- logging : SQL 로그 출력
// media.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([Media, MediaCore]),
S3Module,
QueueModule,
],
TypeOrmModule.forFeature([...])
- 해당 모듈에서 @InjectRepository(Entity)로 레포지토리 주입을 가능하게 한다.
- 그 모듈의 서비스/ 프로바이더가 엔티티 레포를 사용할 때 사용한다.
- 직접 해당 테이블에 데이터 작업을 하지 않고 관계만 사용하는 엔티티는 여기 넣을 필요가 없다.
흔히 발생하는 에러와 트러블 슈팅
1) Entity metadata for X#relation was not found
- 원인: 관계 대상 엔티티가 커넥션에 등록되지 않음, 혹은 다른 경로의 다른 클래스가 등록됨
- 해결
- 루트 entities로 정확한 참조 등록
- 루트에서 명시적으로 등록하고 모듈에서는 필요 레포만 forFeature에 등록
2) synchronize : true로 데이터 날아감
- 실무에서는 꼭 false로 두는 편이 좋다.
3) 마이그레이션이 안 잡힘
- ESM/TSX 패키지 매니저 환경차이로 글로벌 로딩이 실패했을 수 있음, 동적 import로 클래스 배열 을 주입
- 클래스명/타임스탬프 규칙 문제
Repository와 Repository 패턴 (NestJS + TypeORM 관점)
1) Repository 패턴이란?
- 도메인 객체(엔티티)와 데이터 소스(DB) 사이의 추상화 계층
- 애플리케이션은 컬렉션처럼 Repository에 조회와 저장을 요청하고, SQL 연결, 매핑은 Repository가 책임을 진다.
- 서비스/도메인 로직이 SQL 디테일로부터 자유로워지며, DB 교체, 쿼리 최적화 시 Repository 내부만 수정하면 된다.
2) TypeORM의 Repository 기본 API
// Nest: @InjectRepository(Entity) 로 주입
constructor(@InjectRepository(User) private readonly users: Repository<User>) {}
await this.users.find({ where: { email }, take: 20, order: { createdAt: 'DESC' } });
await this.users.findOne({ where: { id } });
await this.users.count({ where: { isActive: true } });
const entity = this.users.create({ email, displayName });
await this.users.save(entity); // insert or update (merge & flush)
await this.users.insert({ email, displayName }); // insert only (빠르고 검증 적음)
await this.users.update({ id }, { isActive: false }); // where update
await this.users.delete({ id }); // hard delete
await this.users.softDelete({ id }); // soft delete (컬럼 필요)
await this.users.restore({ id });
await this.users.upsert({ email }, ['email']); // 충돌키 기준 upsert (0.3.x)
복잡한 조인이나 집계는 QueryBuilder를 사용
const rows = await this.users
.createQueryBuilder('u')
.leftJoin('u.posts', 'p')
.select(['u.id', 'u.displayName'])
.addSelect('COUNT(p.id)', 'postCount')
.groupBy('u.id')
.orderBy('postCount', 'DESC')
.getRawMany();
3) Nest에서 Repository 주입하기
@Module({
imports: [TypeOrmModule.forFeature([User])], // 이 모듈에서 이 엔티티의 Repository를 주입 가능
providers: [UsersService],
exports: [UsersService],
})
- forFeature: 이 모듈에서 레포가 필요한 엔티티만 넣는다.
'NestJS' 카테고리의 다른 글
| [NestJS] Request lifecycle (Middleware, Guards, Interceptors, Pipes, Filters) (0) | 2025.11.05 |
|---|---|
| nestjs와 spec.ts 그리고 테스트코드 (0) | 2025.10.08 |
| [TypeORM] TypeORMError: Migration class name should have a JavaScript timestamp appended. (0) | 2025.08.31 |
| [NestJS] Validation failed (numeric string is expected) (2) | 2025.07.26 |
| [nestJS] Pipe와 커스텀 데코레이터 (feat. typedi) (1) | 2025.07.13 |