NestJS : cannot provide an instance to child module owning the interface - nestjs

I try to decouple the controller module of a NestJs module from a domain module (as per clean architecture principles), so that dependency flows only to Domain.
I do not understand how to make it work, injection of the repo interface's implementation does not happen (error when nest starts).
Here is the module structure :
AppModule -> CommunityControllerModule -> DomainModule
(provides repoImpl (has service requiring an unknown
by token) concrete repo interface impl)
The CommunityControllerModule :
import { DomainModule } from 'libs/domain/src';
import { Module } from '#nestjs/common';
import { CommunityController } from './community.controller';
import { CommunityRepositoryImpl } from './community.repository';
#Module({
imports: [DomainModule, CommunityRepositoryImpl],
controllers: [CommunityController],
exports: [CommunityRepositoryImpl],
providers: [
{
provide: 'CommunityRepository',
useClass: CommunityRepositoryImpl,
},
],
})
export class CommunityControllerModule {}
The DomainModule :
import { Module } from '#nestjs/common';
import { CommunityService } from './community.service';
#Module({
providers: [CommunityService],
exports: [CommunityService],
})
export class DomainModule {}
The CommunityService where injection fails :
import { HttpException, HttpStatus, Inject, Injectable } from '#nestjs/common';
import { CommunityRepository } from './community.repo';
#Injectable()
export class CommunityService {
constructor(
#Inject('CommunityRepository') private repo: CommunityRepository,
) {}
}
The outputted error :
> nest start
webpack 5.28.0 compiled successfully in 8218 ms
[Nest] 7204 - 2021-05-05 20:52:05 [NestFactory] Starting Nest application...
[Nest] 7204 - 2021-05-05 20:52:05 [InstanceLoader] CommunityRepositoryImpl dependencies initialized +49ms
[Nest] 7204 - 2021-05-05 20:52:05 [ExceptionHandler] Nest can't resolve dependencies of the CommunityService (?). Please make sure that the argument CommunityRepository at index [0] is available in the DomainModule context.
Potential solutions:
- If CommunityRepository is a provider, is it part of the current DomainModule?
- If CommunityRepository is exported from a separate #Module, is that module imported within DomainModule?
#Module({
imports: [ /* the Module containing CommunityRepository */ ]
})

What I missed was to set the DomainModule as a dynamic module.
The Domain module becomes :
import { DynamicModule, Module } from '#nestjs/common';
import { CommunityService } from './community.service';
#Module({})
export class DomainModule {
static register(repoImpl): DynamicModule {
return {
module: DomainModule,
imports: [repoImpl],
providers: [
CommunityService,
{
provide: 'CommunityRepository',
useClass: repoImpl,
},
],
exports: [CommunityService],
};
}
}
...and the CommunityControllerModule imports it with the repo implementation :
import { DomainModule } from 'libs/domain/src';
import { Module } from '#nestjs/common';
import { CommunityController } from './community.controller';
import { CommunityRepositoryImpl } from './community.repository';
#Module({
imports: [
DomainModule.register(CommunityRepositoryImpl),
CommunityRepositoryImpl,
],
controllers: [CommunityController],
exports: [CommunityRepositoryImpl],
providers: [
{
provide: 'CommunityRepository',
useClass: CommunityRepositoryImpl,
},
],
})
export class CommunityControllerModule {}

Related

Nest js convert module from forRoot to forRootAsync

#Module({
imports: [],
providers: [SupertokensService, AuthService],
exports: [],
controllers: [AuthController],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
static forRoot({
connectionURI,
apiKey,
appInfo,
}: AuthModuleConfig): DynamicModule {
return {
providers: [
{
useValue: {
appInfo,
connectionURI,
apiKey,
},
provide: ConfigInjectionToken,
},
],
exports: [],
imports: [],
module: AuthModule,
};
}
}
The problem with this implementaion I can't use env variables, so I need useFactory to pass ConfigService. Can somebody do that, and give some explanation.
I figure out how to make this work, unfortunately it only works with nest.js version 9 (current latest version). First you need to create a new file. For example auth.module-definition.ts. Now in this file we need to create ConfigurableModuleBuilder.
import { ConfigurableModuleBuilder } from '#nestjs/common';
import { AuthModuleConfig } from './config.interface';
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<AuthModuleConfig>()
.setClassMethodName('forRoot')
.build();
we need to set setClassMethodName('forRoot'), when we set forRoot it will create two methods, forRoot and forRootAsync. The next step is to extend our created ConfigurableModuleClass. it should look something like this
import { MiddlewareConsumer, Module } from '#nestjs/common';
import { AuthMiddleware } from './auth.middleware';
import { SupertokensService } from './supertokens/supertokens.service';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { ConfigurableModuleClass } from './auth.module-definition';;
#Module({
imports: [],
providers: [SupertokensService, AuthService],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule extends ConfigurableModuleClass {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
}
And that's actually it, so from now on we have forRootAsync and we get reigister it in app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { AuthModule } from './auth/auth.module';
import { ConfigModule, ConfigType } from '#nestjs/config';
import authConfig from './auth/auth.config';
#Module({
imports: [
AuthModule.forRootAsync({
inject: [authConfig.KEY],
imports: [ConfigModule.forFeature(authConfig)],
useFactory: (config: ConfigType<typeof authConfig>) => {
return {
connectionURI: config.CONNECTION_URI,
appInfo: {
appName: config.appInfo.APP_NAME,
apiDomain: config.appInfo.API_DOMAIN,
websiteDomain: config.appInfo.WEBSITE_DOMAIN,
apiBasePath: config.appInfo.API_BASE_PATH,
websiteBasePath: config.appInfo.WEBSITE_BASE_PATH,
},
};
},
})
],
controllers: [],
providers: [
],
})
export class AppModule implements NestModule {
}
here I am using Nest.js Config, but you don't need to, so use it how you want.
I know that my english is not the best, so if you still do not understand you can check these sources https://docs.nestjs.com/fundamentals/dynamic-modules#configurable-module-builder
https://trilon.io/blog/nestjs-9-is-now-available#Configurable-module-builder

Dependency injection is undefined in exported service of Nestjs

I have CurrencyService that I want to use in another module. Here's what I did:
import { HttpModule } from '#nestjs/axios';
import { Module } from '#nestjs/common';
import { ScheduleModule } from '#nestjs/schedule';
import { TypeOrmModule } from '#nestjs/typeorm';
import { CurrencyRepository } from './currency.repository';
import { CurrencyService } from './currency.service';
#Module({
imports: [
HttpModule,
ScheduleModule.forRoot(),
TypeOrmModule.forFeature([CurrencyRepository]),
],
exports: [CurrencyService],
providers: [CurrencyService],
})
export class CurrencyModule {}
In my CurrencyService, I have injected a repository and another service:
export class CurrencyService {
constructor(
private currencyRepository: CurrencyRepository,
private httpService: HttpService,
) {}
async getCurrency(base: string, target: string): Promise<Currency> {
console.log(this.currencyRepository);
.....
My problem is that when I import the CurrencyModule, and inject the CurrencyService into the service of another module, currencyRepository and httpService are both undefined.
There is no error on startup, it's just that the dependencies are undefined which causes error on runtime.
I also tried something like this:
import { HttpModule } from '#nestjs/axios';
import { Module } from '#nestjs/common';
import { ScheduleModule } from '#nestjs/schedule';
import { TypeOrmModule } from '#nestjs/typeorm';
import { CurrencyRepository } from './currency.repository';
import { CurrencyService } from './currency.service';
#Module({
imports: [
HttpModule,
ScheduleModule.forRoot(),
TypeOrmModule.forFeature([CurrencyRepository]),
],
exports: [
CurrencyService,
TypeOrmModule.forFeature([CurrencyRepository]),
HttpModule,
],
providers: [CurrencyService],
})
export class CurrencyModule {}
I got the same undefined dependencies.
The #Injectable() decorator is missing from the CurrencyService. This decorator (or any decorator really) is what tells Typescript to emit the metadata of the controller that Nest is reading, so it is crucial to have it in place.

Nest can't resolve dependencies of the JwtService though exported from another module

I am using #nestjs/jwt in my NestJS project.
I have two modules, AuthModule and AppModule.
The AuthModule uses the #nestjs/jwt
The AppModule invoke auth service from AuthModule.
AuthService:
import { Injectable } from '#nestjs/common';
import { JwtService } from '#nestjs/jwt';
import { ForbiddenException } from '#nestjs/common';
#Injectable()
export class AuthService {
constructor(private readonly jwt: JwtService) {}
async validate(token: string) {
return this.jwt.verify(token);
}
...
}
AuthModule:
import { Module } from '#nestjs/common'
import { TokenService } from './auth.service'
import { ConfigModule, ConfigService } from '#nestjs/config'
import { JwtModule } from '#nestjs/jwt'
#Module({
imports: [
JwtModule.register({
secret: "mysecret",
signOptions: { expiresIn: "60s" },
}),
],
// I provide AuthService & JwtService
providers: [AuthService, JwtService],
// I export AuthService and JwtService
exports: [AuthService, JwtService],
})
export class AuthModule {}
AppModule:
#Module({
imports: [
AuthModule,
...
],
controllers: [AppController],
// I provide AuthService & JwtService also in AppModule
providers: [AppService, JwtService],
})
export class AppModule {}
(The AppController invokes AuthService instance to validate token.)
I constantly get error:
Nest can't resolve dependencies of the JwtService (?). Please make sure that the argument JWT_MODULE_OPTIONS at index [0]
Why is that? Where do I miss?
You don't need to add JwtService to the providers. Nest will look through the current module's providers, then the imported module's exports, and then the global module's exports to find providers that are not immediately in the level it's resolving the providers in. This means that for AuthService it'll look up to the JwtModule's exports and find the JwtService already created, and use that instance. Then in the AppModule you don't need to mention the AuthService or JwtService, just import the AuthModule that exports the AuthService and JwtModule (doing some module re-exporting) and you should be good to go
EDIT: Added module code:
AuthModule
import { Module } from '#nestjs/common'
import { TokenService } from './auth.service'
import { ConfigModule, ConfigService } from '#nestjs/config'
import { JwtModule } from '#nestjs/jwt'
#Module({
imports: [
JwtModule.register({
secret: "mysecret",
signOptions: { expiresIn: "60s" },
}),
],
providers: [AuthService],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
AppModule
#Module({
imports: [
AuthModule,
...
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Nest can't resolve dependencies of the xService

error (cont): Please make sure that the argument PrismaService at index [0] is available in the Assoc_Page_TagModule context.
There are no errors in the code.
When I attempt > npm run start:dev
I initially get: Found 0 errors. Watching for file changes.
Then an error:
[ExceptionHandler] Nest can't resolve dependencies of the Assoc_Page_TagService (?). Please make sure that the argument PrismaService at index [0] is available in the Assoc_Page_TagModule context.
What is the argument at index [0]? Why is it expecting an argument?
Assoc_Page_Tag.service.ts
import { Injectable } from '#nestjs/common';
import { PrismaService } from '../database';
import { Prisma } from '#prisma/client';
#Injectable()
export class Assoc_Page_TagService {
constructor(private readonly prisma: PrismaService) {}
Assoc_Page_TagsAll() {
return Promise.all([
this.prisma.assoc_Page_Tag.findMany()]).then(([records, total]) => {
return {
records,
metadata: { total },
};
});
}
Assoc_Page_Tag.module.ts
import { Module } from '#nestjs/common';
import { Assoc_Page_TagController } from './Assoc_Page_Tag.controller';
import { Assoc_Page_TagService } from './Assoc_Page_Tag.service';
#Module({
imports: [],
controllers: [Assoc_Page_TagController],
providers: [Assoc_Page_TagService],
exports: [Assoc_Page_TagService],
})
export class Assoc_Page_TagModule {}
Assoc_Page_Tag.controller.ts
import {
Body,Controller,Get,Param,ParseIntPipe,Put,
} from '#nestjs/common';
import { Assoc_Page_TagService } from './Assoc_Page_Tag.service';
import { RESOURCE_BASE_ROUTE } from '../constant';
import { Prisma } from '#prisma/client';
const Route = RESOURCE_BASE_ROUTE.assoc_Page_Tag
#Controller()
export class Assoc_Page_TagController {
constructor(private readonly assoc_page_tagService: Assoc_Page_TagService) {}
#Get(`${Route}`)
all() {
return this.assoc_page_tagService.Assoc_Page_TagsAll();
}
}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Assoc_Page_TagModule } from './Assoc_Page_Tag';
#Module({
imports: [
Assoc_Page_TagModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Missing reference to PrismaModule - which exports PrismaService
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from './database';
import { Assoc_Page_TagModule } from './Assoc_Page_Tag';
#Module({
imports: [
PrismaModule,
Assoc_Page_TagModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

NestJS can't resolve a service dependency

I am trying to inject a ContentsService into a SlackModule so that I can use some of the Content functions in my Slack Controller.
This is app.module
#Module({
imports: [
ContentsModule,
ZapierModule,
SlackModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Here is my Contents Module:
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { ContentsController, ZapierController, SlackInteractiveController } from './contents.controller';
import { ContentsService } from './contents.service';
import { ContentSchema } from './content.model';
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Content', schema: ContentSchema }]),
],
controllers: [ContentsController, ZapierController, SlackInteractiveController],
providers: [ContentsService],
exports: [ContentsService],
})
export class ContentsModule {}
This is my Slack Module:
import { Module } from '#nestjs/common';
import { SlackService } from './slack.service';
import { SlackController } from './slack.controller';
import { ContentsService } from '../contents/contents.service';
#Module({
providers: [SlackService],
controllers: [SlackController],
imports: [ContentsService],
})
export class SlackModule {}
And my Slack Controller
import { ContentsService } from '../contents/contents.service'
#Controller('slackevents')
export class SlackController {
constructor(private contentsService: ContentsService) {}
But when I run this code, I get this error:
[Nest] 75628 - 05/22/2020, 7:08 AM [ExceptionHandler] Nest can't resolve dependencies of the ContentsService (?). Please make sure that the argument at index [0] is available in the ContentsService context. +44ms\
What is it that I am doing wrong?
Services and other providers do not belong in the imports array. Only module classes should go in there. You should have imports: [ContentsModule] in your SlackModule and you'll have access to the ContentsService

Resources