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 ?
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)