Search code examples
dependency-injectionnestjstypeorm

Hoe to use Typeorm generic Repository as a provider in other modules


I have Typeorm Generic repository like this

import { Injectable } from '@nestjs/common'
import { IGenericRepository } from '../abstracts/generic-repository'
import {  Repository } from 'typeorm'
@Injectable()
export class GenericRepository<T> implements IGenericRepository<T> {
    private _repository: Repository<T>;

    constructor(repository: Repository<T>) {
        this._repository = repository;
    }

    getAll(): Promise<T[]> {
        return this._repository.find()
    }

    findById(id: any): Promise<T> {
        return this._repository.findOneBy(id)
    }

    create(item: T): Promise<T> {
        return this._repository.save(item)
    }

    update(id: string, item: T) {
        return this._repository
    }
}

and I wanna to use this repository as provider in other services . for example I want to use this in user Module

import { Module } from '@nestjs/common';
import { User } from '../entity/user.entity';
import { GenericRepositoryModule, GenericRepository } from '@app/common';
import { Repository } from 'typeorm'


@Module({
    imports: [
        GenericRepositoryModule
    ],
    providers: [ // I think something is wrong here
        {
            provide: 'userGenericRepository',
            useFactory: (genericRepository: GenericRepository<User>, user: User) => new genericRepository(Repository(user)),
            inject: [GenericRepository,User]
        }
    ],

})
export class UserModule { }

But this doesn't work , there is an error

I think something is wrong with this provider

so, How can I fix this ?


Solution

  • The issue is that you are providing GenericRepository to their own provider.

    A way to solve this is:

    1. Write a function to create the provider token base on the class;
    export function createRepositoryToken(entity: any): string {
      return `${entity}__generic_repository_token`;
    }
    
    1. Write a function to create the provider. This will help you to save a lot of code.
    export function createGenericRepositoryProvider(entity: any): Provider {
      return {
        provide: createRepositoryToken(entity),
        useFactory: (manager: EntityManager) => {
          return new GenericRepository(new Repository(entity, manager));
        },
        inject: [EntityManager],
      };
    }
    
    1. Write a custom decorator
    export const InjectRepo = (entity: any) =>
      Inject(createRepositoryToken(entity));
    
    1. Create the provider in your module.
    @Module({
      imports: [],
      providers: [
        createGenericRepositoryProvider(User);
      ]
    })
    export class UserModule {}
    

    A cleaner option is to write the module like this:

    // user.providers.ts
    export UserRepositoryProvider = createGenericRepositoryProvider(User);
    

    and use it in your module

    @Module({
      imports: [],
      providers: [
        UserRepositoryProvider;
      ]
    })
    export class UserModule {}
    
    1. Inject the provider in your service:
    @Injectable()
    export class UserService {
      constructor(@InjectRepo(User) private readonly repository: GenericRepository<User>)
    }
    

    and is done.

    Update (bonus):

    You can also create a module to provide generic repositories easily.

    export type GenericRepositoryModuleOptions = {
      entities: any[];
    };
    
    @Module({})
    export class GenericRepositoryModule {
      static forFeature(options: GenericRepositoryModuleOptions): DynamicModule {
        const repositoryTokens = this.createRepositoryTokens(options);
        const repositoryProviders = this.createRepositoryProviders(options);
        return {
          module: GenericRepositoryModule,
          providers: repositoryProviders,
          exports: repositoryTokens,
        };
      }
    
      private static createRepositoryTokens(options: GenericRepositoryModuleOptions) {
        return options.entities.map(entity => createRepositoryToken(entity));
      }
    
      private static createRepositoryProviders(
        options: GenericRepositoryModuleOptions
      ): Provider[] {
        return options.entities.map((entity) =>
          createGenericRepositoryProvider(entity)
        );
      }
    }