Reputation: 3241
I'm currently working through the database integration docs for NestJS using TypeOrm. In these docs there are examples that show how to inject a custom database repository using the app.module from NestJS. All of these examples inject classes using the actual type of the custom repository.
@Injectable()
export class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
}
This code is injected via the app.modules by providing a import like such:
@Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
This works well if you are fine with programming against an implementation, but I prefer to use an interface in my classes. I've already found the solution to injecting classes via an interface with NestJS in a previous question, but when I try to inject my custom repository like that, it doesn't seem to instanciate correctly and becomes undefined.
(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined
Because of this, I assume you can only inject customRepositories via the forFeature()
call in the app.module, but that won't allow me to use interfaces for injection, as far as I know. Is there any other way I can inject a custom TypeOrm repository without having the replace all my interfaces for the implementation of my custom repository? Thanks in advance!
Edit
Here is my current code, I managed to get it to inject, but this still forces me to use the implementation instead of the interface each time I call the constructor. This is mainly an issue when testing due to mocking.
@CommandHandler(FooCommand)
export class FooHandler
implements ICommandHandler<FooCommand> {
private fooRepository: IFooRepository; // Using Interface as a private property.
private barEventBus: IEventBus;
constructor(fooRepository: FooRepository,
barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
this.fooRepository = fooRepository;
this.barEventBus = barEventBus;
}
@EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {
getFoo() {
// Do stuff
}
}
@Module({
imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],
// Other module setup
})
export class AppModule {}
Upvotes: 2
Views: 20340
Reputation: 366
Edit: This answer is crap, that abstract-class-as-interface hack used does not work out as the defined methods seem to be optional to implement despite being marked as abstract.
Well, kind of got it working. Based on this answer https://stackoverflow.com/a/74561702/913136 I used an abstract class as interface (you can actually implement
it) for not being required to pass strings around as tokens. Only drawback is the misuse of the abstract class. Not sure yet if I like it.
Using an actual interface in the same way seems not to be possible unfortunately. Urgh.
@Module({
imports: [
TypeOrmModule.forRoot({
...dataSource.options,
autoLoadEntities: true,
}),
TypeOrmModule.forFeature([Listing]),
],
controllers: [ViewListingController],
providers: [
{
provide: ListingRepository,
useClass: TypeOrmListingRepository,
},
],
})
makeshift interface:
import { Listing } from "./Listing";
export abstract class ListingRepository {
abstract findMostRecent: () => Promise<Listing[]>;
}
implementation:
import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "@nestjs/common";
import { Repository, DataSource } from "typeorm";
@Injectable()
export class TypeOrmListingRepository
extends Repository<Listing>
implements ListingRepository
{
constructor(private dataSource: DataSource) {
super(Listing, dataSource.createEntityManager());
}
findMostRecent() {
return this.find({});
}
}
import { Controller, Get } from "@nestjs/common";
import { ListingRepository } from "../Domain";
@Controller("listings")
export class ViewListingController {
constructor(private readonly listingRepo: ListingRepository) {}
@Get("/most-recent")
listMostRecent() {
return this.listingRepo.findMostRecent();
}
}
Upvotes: -1
Reputation: 1134
It should work with using the InjectRepository
decorator where you specify the Repository but then you type is as your interface instead and when testing you just provide the IFooRepository
!
Example code:
constructor(@InjectRepository(FooRepository) fooRepository: IFooRepository,
barEventBus: EventBus) {
Upvotes: 5