NestJS@9.x.x & TypeORM@0.3.x에서 customRepository 쉽게 사용하기
TypeORM의 버전 변경으로 인한 혼란.
typeorm의 버전이 0.3.x 로 올라가면서 0.2와는 너무나도 다른 많은 변경들이 생겼다. 이것은 혼란 그 잡채였다.
기존 0.2 버전에서는 @EntityRepository를 이용해서 커스텀 레포지토리를 만들어서 쓸 수 있었다면 0.3버전에서는 @EntityRepository가 deprecated되어서 사용할 수 없었다.
커스텀 레포지토리는 필수적 요소라고 생각하기 때문에 typeorm 버전 up이 되더라도 무조건 쓸 수 있게 설정 해놔야 한다고 생각했다. 그래서 여러가지 방법들을 찾아보려고 노력하였다. 두가지 방법에 대하여 찾아봤는데 그 전에 레포지토리가 무엇인지 먼저 살펴보도록 하겠다.
Repository 패턴이란?

정의
리포지토리는 데이터 원본에 액세스하는 데 필요한 논리를 캡슐화하는 클래스 또는 구성 요소입니다. 리포지토리는 공통 데이터 액세스 기능에 집중해 더 나은 유지관리를 제공하고 도메인 모델 계층에서 데이터베이스에 액세스하는 데 사용되는 기술이나 인프라를 분리합니다. Entity Framework와 같은 ORM(개체 관계 매핑)을 사용하는 경우 LINQ 및 강력한 형식화 덕분에 구현해야 할 코드가 간소화됩니다. 이렇게 하면 데이터 액세스 내부 작업보다 데이터 지 속성 논리에 더 집중하게 합니다.
간략하게 db와 관련된 일을 서비스단에서 하는것이 아닌 repository에서 다루어서 서비스단에서는 db의 쿼리들이 노출되지 않는다라는 것이다.
Custom Repository 찾은 방법
첫번째 방법
간단한 방법으로 다른 provider들과 똑같이 쓰는 것이다.
user.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService, UserRepository],
})
export class UserModule {}
user.repository.ts
@Injectable()
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
async findByUsername(username: string): Promise<User> {
const result = await this.createQueryBuilder('user')
.where('user.username = :username', { username: username })
.getOne();
return result;
}
}
user.service.ts
@Injectable()
export class UserService {
constructor(
private userRepository: UserRepository,
) {}
async findByUsername(username: string): Promise<User> {
return this.userRepository.findByUsername(username);
}
}
Repository에서 dataSource를 injection 받는 방법인 것이다.
constructor의 기본 짜임새는 아래와 같다.
constructor(target: EntityTarget<Entity>, manager: EntityManager, queryRunner?: QueryRunner);
두번째 방법
두번째 방법은 현재 나는 에러가 나는데 다른 분들은 잘 되신건지 공유를 하신 방법이다. 나는 해결을 못해서 일단 공유는 하는데 혹시나 빠진 부분이 있거나 실수한 부분이 있다면 댓글 부탁드립니다.
두번째 방법으로는 @EntityRepository와 유사하게 만든 @CustomRepository를 사용하는 방법이다.
우선 소스에서 @CustomRepository 데코레이터와 module을 만들어준다.
src/db/typeorm.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const TYPEORM_CUSTOM_REPOSITORY = 'TYPEORM_CUSTOM_REPOSITORY';
// eslint-disable-next-line @typescript-eslint/ban-types
export function CustomRepository(entity: Function): ClassDecorator {
return SetMetadata(TYPEORM_CUSTOM_REPOSITORY, entity);
}
src/db/typeorm-custom.module.ts
import { DynamicModule, Provider } from '@nestjs/common';
import { getDataSourceToken } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { TYPEORM_CUSTOM_REPOSITORY } from './typeorm.decorator';
export class TypeOrmCustomModule {
public static forCustomRepository<T extends new (...args: any[]) => any>(
repositories: T[],
): DynamicModule {
const providers: Provider[] = [];
for (const repository of repositories) {
const entity = Reflect.getMetadata(TYPEORM_CUSTOM_REPOSITORY, repository);
if (!entity) {
continue;
}
providers.push({
inject: [getDataSourceToken()],
provide: repository,
useFactory: (dataSource: DataSource): typeof repository => {
const baseRepository = dataSource.getRepository<any>(entity);
return new repository(
baseRepository.target,
baseRepository.manager,
baseRepository.queryRunner,
);
},
});
}
return {
exports: providers,
module: TypeOrmCustomModule,
providers,
};
}
}
user.repository.ts
@CustomRepository(User)
export class UserRepository extends Repository<User> {}
user.module.ts
@Module({
imports: [TypeOrmCustomModule.forCustomRepository([UserRepository])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
이렇게 해주면 0.2.x 버전의 @EntityRepository와 유사하게 사용할 수 있다.
하지만 난 되지 않아서 첫번째 방법으로 해줬다.나의 두번째 방법 에러는 아래와 같다.
[Nest] 160316 - 2022. 10. 19. 오후 12:21:50 ERROR [ExceptionHandler] Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index [0] is available in the UserModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current UserModule?
- If UserRepository is exported from a separate @Module, is that module imported within UserModule?
@Module({
imports: [ /* the Module containing UserRepository */ ]
})
Error: Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserRepository at index [0] is available in the UserModule context.
Potential solutions:
- If UserRepository is a provider, is it part of the current UserModule?
- If UserRepository is exported from a separate @Module, is that module imported within UserModule?
@Module({
imports: [ /* the Module containing UserRepository */ ]
})
at Injector.lookupComponentInParentModules (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:241:19)
at Injector.resolveComponentInstance (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:194:33)
at resolveParam (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:116:38)
at async Promise.all (index 0)
at Injector.resolveConstructorParams (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:131:27)
at Injector.loadInstance (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:57:13) at Injector.loadProvider (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\injector.js:84:9)
at async Promise.all (index 3)
at InstanceLoader.createInstancesOfProviders (D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\instance-loader.js:47:9)
at D:\workspace\nestjs-without-typeorm\nestjs9\node_modules\@nestjs\core\injector\instance-loader.js:32:13
이렇게 NestJS@9.x.x & TypeORM@0.3.x에서 customRepository를 사용하는 방법에 대하여 알아보았다.
