Skip to content

Instantly share code, notes, and snippets.

@anchan828
Last active May 1, 2024 04:26
Show Gist options
  • Star 94 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
  • Save anchan828/9e569f076e7bc18daf21c652f7c3d012 to your computer and use it in GitHub Desktop.
Save anchan828/9e569f076e7bc18daf21c652f7c3d012 to your computer and use it in GitHub Desktop.
This is an improvement to allow @nestjs/typeorm@8.1.x to handle CustomRepository. I won't explain it specifically, but it will help in some way. https://github.com/nestjs/typeorm/pull/1233

You need to provide some classes and decorators yourself to maintain the same style as typeorm@2.x.

1. EntityRepository -> CustomRepository

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

@CustomRepository(UserEntity)
export class UserRepository extends Repository<UserEntity> {}

2. forFeature -> forCustomRepository

@Module({
  exports: [UserService],
  imports: [TypeOrmModule.forFeature([UserRepository])],
  providers: [UserService],
})
export class UserModule {}

@Module({
  exports: [UserService],
  imports: [TypeOrmExModule.forCustomRepository([UserRepository])],
  providers: [UserService],
})
export class UserModule {}
@Injectable()
export class DatabaseOptions implements TypeOrmOptionsFactory {
public createTypeOrmOptions(): TypeOrmModuleOptions {
return {
...
entities: [UserEntity],
};
}
}
import { SetMetadata } from "@nestjs/common";
export const TYPEORM_EX_CUSTOM_REPOSITORY = "TYPEORM_EX_CUSTOM_REPOSITORY";
export function CustomRepository(entity: Function): ClassDecorator {
return SetMetadata(TYPEORM_EX_CUSTOM_REPOSITORY, entity);
}
import { DynamicModule, Provider } from "@nestjs/common";
import { getDataSourceToken } from "@nestjs/typeorm";
import { DataSource } from "typeorm";
import { TYPEORM_EX_CUSTOM_REPOSITORY } from "./typeorm-ex.decorator";
export class TypeOrmExModule {
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_EX_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: TypeOrmExModule,
providers,
};
}
}
import { Module } from "@nestjs/common";
import { TypeOrmExModule } from "../database/typeorm-ex.module";
import { UserRepository } from "./user.repository";
import { UserService } from "./user.service";
@Module({
exports: [UserService],
imports: [TypeOrmExModule.forCustomRepository([UserRepository])],
providers: [UserService],
})
export class UserModule {}
import { Repository } from "typeorm";
import { CustomRepository } from "../database/typeorm-ex.decorator";
import { UserEntity } from "./user.entity";
@CustomRepository(UserEntity)
export class UserRepository extends Repository<UserEntity> {}
import { Injectable } from "@nestjs/common";
import { UserRepository } from "./user.repository";
@Injectable()
export class UserService {
constructor(private readonly repository: UserRepository) {}
}
@ricbermo
Copy link

I just found a way that, IMHO, it's cleaner and easier to use/test https://github.com/leosuncin/nest-typeorm-custom-repository

Thanks for sharing, tho.

@khasanovdiyor
Copy link

@anchan828 Hi, this helped me a lot thank you, but how can mock this in tests? it's not a provider and the way I usually do was getRepositoryToken(Model) and it doesn't work.

@anchan828
Copy link
Author

@khasanovdiyor If you have a UserRepository, you can use { provide: UserRepository, useValue: createMock() }.

@CustomRepository(UserEntity)
export class UserRepository extends Repository<UserEntity> {}

@khasanovdiyor
Copy link

@anchan828 hey thanks for the reply, yeah I was using it like that, but wasn't working, but I figured out that problem was when getting the instance with module.get(), anyways thank you!

@ffind
Copy link

ffind commented Aug 16, 2022

Hi @anchan828, I'm new in nest, what DatabaseOptions is for?

@zamanzamzz
Copy link

@anchan828 Thanks for this, I found it very helpful! It saved me quite a bit of refactoring time.

@ffind DatabaseOptions can be used in the following away in your root app module:

TypeOrmModule.forRootAsync({
  useClass: DatabaseOptions,
});

You can take a look at the documentation here: https://docs.nestjs.com/techniques/database#async-configuration

@Siusarna
Copy link

Hey everyone. Any idea how to use method from CustomRepository1 in CustomRepository2 ? I tried to inject CustomRepository1 in constructor, but then I should call super() and pass the params which I don't know :(

@anchan828
Copy link
Author

@Siusarna If you adopt my method, you cannot do Inject because CustomRepository simply creates an instance from a Class. You must manually create a Repository from the datasource.

I don't know if it actually works, but this is what it looks like.

@EntityCustomRepository(UserEntity)
export class UserRepository extends Repository<UserEntity> {

  customRepository2: CustomRepository2;

  constructor(target: EntityTarget<UserEntity>, manager: EntityManager, queryRunner?: QueryRunner) {
    super(target, manager, queryRunner);

    const baseRepository = manager.getRepository<any>(CustomEntity2);
    this.customRepository2 = new CustomRepository2(baseRepository.target, baseRepository.manager, baseRepository.queryRunner);
  }
}

@Siusarna
Copy link

Thanks a lot ❤️. I will try it out

@Rainson12
Copy link

any idea on how to make use of custom repositories when working with transactions using

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

and then pass queryRunner to the service where we normally would do:
const userRepository: UserRepository = queryRunner.manager.getCustomRepository(UserRepository)

however by now using customRepositories from this github thread the queryRunner no longer holds any instances of the repositories and thus throwing the error:
Custom repository UserRepository was not found. Did you forgot to put @EntityRepository decorator on it?

@tiagoblackcode
Copy link

any idea on how to make use of custom repositories when working with transactions using

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

and then pass queryRunner to the service where we normally would do: const userRepository: UserRepository = queryRunner.manager.getCustomRepository(UserRepository)

however by now using customRepositories from this github thread the queryRunner no longer holds any instances of the repositories and thus throwing the error: Custom repository UserRepository was not found. Did you forgot to put @EntityRepository decorator on it?

Can you do something like this?

@Rainson12
Copy link

Rainson12 commented Sep 6, 2022

any idea on how to make use of custom repositories when working with transactions using

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

and then pass queryRunner to the service where we normally would do: const userRepository: UserRepository = queryRunner.manager.getCustomRepository(UserRepository)
however by now using customRepositories from this github thread the queryRunner no longer holds any instances of the repositories and thus throwing the error: Custom repository UserRepository was not found. Did you forgot to put @EntityRepository decorator on it?

Can you do something like this?

apparently not since the queryRunner is created using annotation like:

@Injectable()
export class TransactionInterceptor implements NestInterceptor {
  constructor(private readonly dataSource: DataSource) {}

  public async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const httpContext: HttpArgumentsHost = context.switchToHttp()
    const queryRunner: QueryRunner = this.dataSource.createQueryRunner()
    const request: RequestWithTransactionQueryRunner = httpContext.getRequest()
    request.queryRunner = queryRunner
    await request.queryRunner.connect()
    await request.queryRunner.startTransaction()
    return next.handle().pipe(
      finalize(async () => {
        await queryRunner.commitTransaction()
        await queryRunner.release()
      }),
      catchError(async error => {
        await queryRunner.rollbackTransaction()
        throw error
      })
    )
  }
}


@tiagoblackcode
Copy link

tiagoblackcode commented Sep 6, 2022

How about

queryRunner.manager.withRepository(this.myCustomRepository)

where this.myCustomRepository is the injected custom repository? I was able to use that strategy successfully. The EntityManager#withRepository method re-creates the repository and assigns both the EntityManager and the QueryRunner instances to it.

Note: I'm using this strategy with the entityManager.transaction((em) => ...) method so I haven't tested with the QueryRunner#manager approach.

@Rainson12
Copy link

How about

queryRunner.manager.withRepository(this.myCustomRepository)

where this.myCustomRepository is the injected custom repository? I was able to use that strategy successfully. The EntityManager#withRepository method re-creates the repository and assigns both the EntityManager and the QueryRunner instances to it.

Note: I'm using this strategy with the entityManager.transaction((em) => ...) method so I haven't tested with the QueryRunner#manager approach.

great Idea, didnt know about it, it does in fact work. Thanks!

@ab-etemadi
Copy link

ab-etemadi commented Sep 13, 2022

Hi everyone, any idea on How can I use QueryBuilder in user.repository.ts too .

@foliveirafilho
Copy link

Hey everyone. If you want to use transactions in a simpler way, there's a pretty cool lib that uses cls-hooked for that:

But we have to modify how custom repositories are provided in typeorm-ex.module.ts as suggested by the author of the lib:

Hope this can be helpful to someone. It worked great for me!

@rhutchison
Copy link

I created a utility method to provide custom repositories. Open to feedback: https://gist.github.com/rhutchison/a530d89c37f1978a48dcee4bf2418cb7

@vnxdev
Copy link

vnxdev commented Sep 29, 2022

import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private readonly dataSource: DataSource) {
    super(User, dataSource.manager);
  }
}

Should do the trick. Don't forget to add UserRepository as a provider of the module.

@AntoineMaitre
Copy link

Hello, thanks for your solution, works great to bridge from nestjs v8.x / typeorm 0.2.x to nestjs 9.x / typeorm 0.3.x, however, how do you deal with custom repositories imports / mocks within Unit tests / e2e ?

Thanks in advance fo any insight

@GaryHebert
Copy link

GaryHebert commented Oct 25, 2022

Thank you for this solution for managing custom repos.

I have custom repositories and entities in a library, that I install using npm.

Datasources are defined in the client project, so from the client I name the DataSource, and use the name as parameter of TypeOrmExModule.forCustomRepository.

But I get an error saying Nest can't resolve dependencies of the MyEntityRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmExModule context.

This MyEntityRepository does not exist, but MyEntity and MyRepository do.

Is there a way to fix this?

I tried to add DataSource to list of providers, but then I get:

TypeError: Cannot read properties of undefined (reading 'name') at new DataSource (D:\xxx\src\data-source\DataSource.ts:133:29) at Injector.instantiateClass (D:\xxx\node_modules\@nestjs\core\injector\injector.js:340:19) at callback (D:\xxx\node_modules\@nestjs\core\injector\injector.js:53:45) at Injector.resolveConstructorParams (D:\xxx\node_modules\@nestjs\core\injector\injector.js:132:24) at Injector.loadInstance (D:\xxx\node_modules\@nestjs\core\injector\injector.js:57:13) at Injector.loadProvider (D:\xxx\node_modules\@nestjs\core\injector\injector.js:84:9) at async Promise.all (index 3) at InstanceLoader.createInstancesOfProviders (D:\xxx\node_modules\@nestjs\core\injector\instance-loader.js:47:9) at D:\xxx\node_modules\@nestjs\core\injector\instance-loader.js:32:13 at async Promise.all (index 6)

Thanks a lot.

@godtaehee
Copy link

@GaryHebert What do you mean??? You mean that you import like this???:

TypeOrmExModule.forCustomRepository([DataSource])

@godtaehee
Copy link

@anchan828 Hey, Thanks for your answer. I use it and works perfect.

@GaryHebert
Copy link

@GaryHebert What do you mean??? You mean that you import like this???:

TypeOrmExModule.forCustomRepository([DataSource])

I modified the TypeOrmExModule like @anchan828 suggestion on june 10th, adding the dataSourceName parameter.

@lucaslgr
Copy link

lucaslgr commented Dec 7, 2022

Firstly, thanks for the gist @anchan828

I don't know nothing about nest js yet and I need to make this update. I did everything but in my service I was injecting the repository with this decorator @InjectRepository as below:

@Injectable()
export class ABCService {

  constructor(
    @InjectRepository(ABCRepository, 'FDM')
    private readonly abcRepository: ABCRepository
  ) {}
.
.
.
}

And after the modifications I'm getting this error:
Nest can't resolve dependencies of the ABCService (?). Please make sure that the argument ABC_ABCRepository at index [0] is available in the ABCModule context.

How can i fix this?
I've tried to manually construct the repository inside the service constructor, but, I don't have any of the necessary arguments there, as EntityManager and QueryRunner.

@darrenm-peppy
Copy link

import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private readonly dataSource: DataSource) {
    super(User, dataSource.manager);
  }
}

Should do the trick. Don't forget to add UserRepository as a provider of the module.

This will work for some use cases. But it wont work if you're using transactions, because the repository constructor is called directly inside withRepository().

@darrenm-peppy
Copy link

Unfortunately, the simpler solution (as recommended by @kamilmysliwiec here nestjs/typeorm#1422) does not work with transactions.

@happytalkKO
Copy link

Any idea for multiple database connection with custom repositories?

@hamza-HL
Copy link

import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private readonly dataSource: DataSource) {
    super(User, dataSource.manager);
  }
}

Should do the trick. Don't forget to add UserRepository as a provider of the module.

It works for me, thanks!

@eric-ho-github
Copy link

Firstly, thanks for the gist @anchan828

I don't know nothing about nest js yet and I need to make this update. I did everything but in my service I was injecting the repository with this decorator @InjectRepository as below:

@Injectable()
export class ABCService {

  constructor(
    @InjectRepository(ABCRepository, 'FDM')
    private readonly abcRepository: ABCRepository
  ) {}
.
.
.
}

And after the modifications I'm getting this error: Nest can't resolve dependencies of the ABCService (?). Please make sure that the argument ABC_ABCRepository at index [0] is available in the ABCModule context.

How can i fix this? I've tried to manually construct the repository inside the service constructor, but, I don't have any of the necessary arguments there, as EntityManager and QueryRunner.

Hi mate, I hit my head into the same issue as yours.
What was your solution for it?
thanks

@Philipinho
Copy link

And after the modifications I'm getting this error: Nest can't resolve dependencies of the ABCService (?). Please make sure that the argument ABC_ABCRepository at index [0] is available in the ABCModule context.
How can i fix this? I've tried to manually construct the repository inside the service constructor, but, I don't have any of the necessary arguments there, as EntityManager and QueryRunner.

Hi mate, I hit my head into the same issue as yours. What was your solution for it? thanks

Add the Repository to the module providers as you would with the service.
e.g

providers: [UserService, UserRepository],

Here is my setup.

user.respository.ts

import { DataSource, Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private dataSource: DataSource) {
    super(User, dataSource.createEntityManager());
  }
  async findByEmail(email: string) {
    return this.findOne({ where: { email: email } });
  }
}

user.module.ts

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UserRepository } from './repositories/user.repository';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UserController],
  providers: [UserService, UserRepository],
})
export class UserModule {}

Now, I need it in my auth module.
auth.module.ts

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
  ],
  controllers: [AuthController],
  providers: [AuthService, TokenService, UserService, UserRepository],
})
export class AuthModule {}

Using it in my auth service.
auth.service.ts

@Injectable()
export class AuthService {
  constructor(
    private userRepository: UserRepository,
    private userService: UserService,
  ) {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment