1. 패키지 다운로드

pnpm install typeorm-transactional

2. main.ts 에 추가

async function bootstrap() {
  initializeTransactionalContext();
  const app = await NestFactory.create(AppModule, {
    abortOnError: true,
  });
  await app.listen(3001);
}
bootstrap();

3. app.module에 추가

@Module({
  imports: [
    TypeOrmModule.forRootAsync(
      {
        useFactory: () => getConfig(),
        dataSourceFactory: async function () {
          return addTransactionalDataSource(new DataSource(getDataSourceConfig()));
        },
      }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

4. config 파일 분리

src/common/database/configuration.ts

export const getConfig = (): TypeOrmModuleOptions => {
  return {
    type: 'mariadb',
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT),
    username: process.env.USERNAME,
    password: process.env.PASSWORD,
    database: process.env.DATABASE,
    logging: process.env.LOGGING === 'true',
    entities: [process.env.ENTITIES],
    synchronize: process.env.SYNCHRONIZE === 'true',
  }
}

export const getDataSourceConfig = (): DataSourceOptions => {
  return {
    type: 'mariadb',
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT),
    username: process.env.USERNAME,
    password: process.env.PASSWORD,
    database: process.env.DATABASE,
    logging: process.env.LOGGING === 'true',
    entities: [process.env.ENTITIES],
    synchronize: process.env.SYNCHRONIZE === 'true',
  }
}

5. 사용

@Injectable()
export class ProductService {
  constructor(
    @InjectRepository(Product)
    private readonly productRepository: Repository<Product>,
    @InjectRepository(Category)
    private readonly categoryRepository: Repository<Category>,
    @InjectRepository(ProductImage)
    private readonly productImageRepository: Repository<ProductImage>,
  ) {}

  @Transactional()
  async saveProduct(request: ProductSaveRequest) {
    const category = await this.categoryRepository.findOneBy({ id: request.categoryId });
    const product = await this.productRepository.save(request.toEntity(category));
    const productImages = await this.productImageRepository.save(
      request.productImages.map(
        (productImage) => ProductImage.of(productImage, product)
      ));
    return ProductResponse.of(category, product, productImages);
  }
  
  // ...

}

실행 로그를 보면 로직 첫 부분에서 트랜잭션이 시작되고 로직 종료 시점에 커밋 or 롤백이 되는 것을 확인할 수 있다