NodeJS 에서 TypeORM 0.3.x 사용하기 (feat. NestJS)
TypeORM 0.3.x 업데이트로 인해 변경된 내용 내 코드에 반영하기
  • Backend

ORM이란

ORM 이란 무엇일까에 대한 내용은 또 한 번 설명하기보다는 이전에 작성되었던 포스트로 대체합니다.
[게시글 바로가기]

왜 TypeORM을 선택했는가

NodeJS 환경에서 사용할 수 있는 orm 종류는 여럿 존재합니다. 구글에 NodeJS ORM이라고만 검색해 봐도 아마 다양한 종류의 ORM이 검색될 것입니다.

대표적으로 많이 사용되는 ORM 종류들로는

  • TypeORM
  • Sequelize
  • Prisma

와 같은 것들이 검색됩니다. 문법을 보며 취향에 맞는 걸 선택하거나 혹은 대중적으로 더 많이 사용되는 걸 선택하면 됩니다.

지금까지 개발해왔던 프로젝트에서는 ORM의 고수준 프레임워크보다는 중간 수준의 Builder 형태를 선호했어요. (여러 가지 이유들이 있었겠지만 구성원들의 러닝 커브에 대한 고민이 많았습니다.) 그러다 최근 프로젝트를 바닥부터 새로 세팅할 일이 생겼고, 나를 포함해 함께 일하는 동료들의 커리어 발전을 위해 ORM을 사용해 보는 것도 괜찮겠다는 생각을 하게 되었고, 여러 가지 ORM 중에서 TypeORM을 선택하게 되었습니다.

TypeORM을 선택했던 이유는 다운로드 수가 많다는 점, 최근까지도 지속적으로 유지 보수가 되어가는 점, 문서화가 잘 되어있다는 점들이 있었습니다.

TypeORM 0.3.0

TypeORM은 지난 2022년 3월 0.3.0 버전을 새롭게 배포했습니다. (왜 아직 1.0.0으로 올라가지 않았는지는 모르겠네요.) 그러면서 많은 함수들이 deprecated 되었으나 아직도 많은 검색 자료에서나, 새로 구축하는 프로젝트에서 deprecated된 함수를 그대로 사용하는 사례를 적지 않게 찾을 수 있었습니다.

이 글에서는 대표적으로 어떤 함수들이 deprecated 되었는지 살펴보고 이를 다른 어떤 함수로 대체하여 사용할 수 있는지 정리해 보고자 합니다.

deprecated

@EntityRepository

EntityRepository는 TypeORM을 사용한다면 대부분의 프로젝트에서 사용되었을 거라 생각되는 어노테이션 중 하나입니다.

@EntityRepository()
class UserRepository extends Repository<User> {}

와 같은 식으로 사용했습니다. 하지만 EntityRepositry가 deprecated 되었고, 이는 다음과 같이 해결할 수 있습니다.

const userRepository = dataSource.getRepository(User).extend({})

만약 NestJS를 사용하고 있다면,

@Injectable()
export class UserRepository {
  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
  ) {}
}

@Injectable() 을 사용하여 기존의 방식 그대로 사용할 수도 있고, 이 외에도 검색해 보면 CustomAnnotation 을 만들어서 사용하는 방법도 있습니다. (검색해 보면 글이 많이 존재하므로 자세히 설명하지 않겠습니다.)

find - select

.find 함수에서 select의 사용방식이 변경되었습니다.

userRepository.find({
  select: ["id", "firstName", "lastName"]
})

==>

userRepository.find({
  select: {
    id: true,
    firstName: true,
    lastName: true,
  }
})

where

where에서 큰 변화라고 한다면 nested 를 지원한다는 것입니다.

userRepository.find({
    where: {
        photos: {
            album: {
                name: "profile"
            }
        }
    }
})

DataSource

DataSource를 여러 개 연결하면 여러 개의 DB에 연결할 수 있습니다. MySQL, MongoDB에 각각 연결하는 것이 가능해집니다.

export const dataSource1 = new DataSource({ /*...*/ })
export const dataSource2 = new DataSource({ /*...*/ })
export const dataSource3 = new DataSource({ /*...*/ })

참고로, MySQL의 main, replica 의 연결을 위해 DataSource 를 각각 만드는것은 추천하지 않습니다.

트랜잭션 처리

이전에 TypeORM에서 트랜잭션 처리를 위해서는 service 레벨에서 @Transaction의 어 로테이션을 사용했었습니다. 하지만 이 어 로테이션은 TypeORM에서 사용을 권장하지 않고 있습니다. 그 이유로는 어 로테이션을 사용하지 말고, 명시적으로 트랜잭션 함수를 분리하라는 것이죠.

저는 어떻게 하면 좀 더 편하게 사용해 볼 수 있을까 고민하다가, 나는 이런 구조를 사용하고 있습니다.

# repository
runTransaction(tx: (entityManager: EntityManager) => Promise<unknown>): Promise<{}> {
    return new Promise(async (resolve) => {
      await this.repository.manager.transaction(tx);
      resolve({});
    });
  }
  
# service
await repository.runTransaction(async (trx) => {
  await repository.insert(data, trx);
  await repository2.insert(data, trx);
  await repository3.updateById(id, data, trx);
});

와 같이 사용할 수 있도록 runTransaction 함수를 생성할 수 있습니다. (더 좋은 구조가 있다면 언제든 지적은 감사합니다)

참고

  • 0.3.0에서 deprecated 처리된 함수들은 0.4.0에서 완전히 삭제됩니다. 업데이트를 미루던지 새로운 함수로 적용해야 합니다.
  • TypeORM 0.3.0 PR