NestJS

[NestJS] TypeOrmModule 다루기 (Repository)

sihanni 2025. 8. 31. 23:43

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: 이 모듈에서 레포가 필요한 엔티티만 넣는다.