Search code examples
typescriptoopnestjstypeorm

NestJS typeorm generic inheritance of Repository not working, this.function is not a function


Iam trying to create a custom repository class that extends Repository so I can add custom log functions to be used from all repositories. here is the code.

user.service.ts:

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: BaseRepository<User>,
    ) {}

    async update(id: string, data: UpdateUserDto): Promise<UpdateResult> {
        return await this.userRepository.updateAndLog(id, data);
    }
}

BaseRepository.ts:

import { Repository, UpdateResult } from 'typeorm';


export class BaseRepository<T> extends Repository<T> {

  async updateAndLog(id: string, data: any): Promise<UpdateResult> {
    const entity = await this.findOne(id as any);
    const savedEntity = await this.update(id, data);
    // log the data here
    return savedEntity;
  }
}

so the output of the function is always:

[Nest] 13820  - 04/11/2023, 12:07:07 PM   ERROR [ExceptionsHandler] this.userRepository.updateAndLog is not a function

I read typeorm documentation about custom repositories: https://typeorm.io/custom-repository#how-to-create-custom-repository

StackOverflow:

github: https://github.com/typeorm/typeorm/issues/2097

Yet nothing is working, is there something wrong with the following code ?


Solution

  • The problem here comes from the @InjectRepository(User) : it injects an instance of Repository instead of the BaseRepository one.

    I found this repository that provide a way to override the repository provided by the TypeORM module, but only for a specific entity.

    However, we could adapt his approach to provide a generic repository instead :

    @Module({
      imports: [TypeOrmModule.forFeature([User])],
      providers: [buildCustomRepositoryProvider<User>(User), UserService],
    })
    export class UserModule {
    }
    

    With a helper file like this :

    import { DataSource, Repository, UpdateResult } from 'typeorm';
    import { Provider } from '@nestjs/common';
    import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm';
    import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
    
    export interface BaseRepository<T> extends Repository<T> {
      this: Repository<T>;
    
      updateAndLog(id: string, data: any): Promise<UpdateResult>;
    }
    
    export function buildCustomRepositoryMethods<T>(): Pick<BaseRepository<T>, 'updateAndLog'> {
      return {
        async updateAndLog(id: string, data: any): Promise<UpdateResult> {
          const entity = await this.findOne({ where: { id: id as any } });
          const savedEntity = await this.update(id, data);
          // log the data here
          return savedEntity;
        },
      };
    }
    
    export function buildCustomRepositoryProvider<T>(entity: EntityClassOrSchema): Provider {
      return {
        provide: getRepositoryToken(entity),
        inject: [getDataSourceToken()],
        useFactory: (dataSource: DataSource) => {
          // Override the default repository with a custom one
          return dataSource.getRepository(entity).extend(buildCustomRepositoryMethods<T>());
        },
      };
    }
    

    Therefore, the @InjectRepository(User) will inject an instance of Repository<User> extended with the methods provided by the BaseRepository interface.

    (Note : using the extend method here to create a custom repository as it's the recommended way since TypeORM 0.3, see here)