Dynamic ConfigModule cannot Inject in custom JwtModule - nestjs

I created my Dynamic configModule to extract environment variables from a different path. It extracts from an yml file. Everything works properly if a add in some module. Here is my ConfigModule:
import { DynamicModule } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { EnvConfigService } from './env.config.service';
export class EnvConfigModule {
/**
* Create an static function to call directly from the class without instantiation
* #param options: Our config module attributes or properties
* #returns DynamicModule
*/
static register(options): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
EnvConfigService,
],
exports: [EnvConfigService],
};
}
}
Now when I want to add that configuration in the new custom JwtModule, CustomJwtModule:
...
import { EnvConfigModule } from 'src/utils/environment/env.config.module';
import { EnvConfigService } from 'src/utils/environment/env.config.service';
#Module({
imports: [
JwtModule.registerAsync({
imports: [EnvConfigModule],
inject: [EnvConfigService],
useFactory: (configService: EnvConfigService) => {
const base64_pubKey = configService.get(ACCESS_PUBLIC_KEY_NAME);
return {
publicKey: Buffer.from(base64_pubKey, KEY_ENCODING),
signOptions: {
algorithm: ENCRYPTION_ALGORITHM,
},
};
},
}),
],
providers: [
{
provide: JWT_ACCESS_SERVICE,
useExisting: JwtService,
},
],
exports: [JWT_ACCESS_SERVICE],
})
export class JWTAccessModule {}
And here is my error:
Error: Nest can't resolve dependencies of the JWT_MODULE_OPTIONS (?). Please make sure that the argument EnvConfigService at index [0] is available in the JwtModule context.
I guess, I do get that error because it does not inject properly in JWT module my service.
My app.module.ts is implemented in that way
...
import { EnvConfigModule } from './utils/environment/env.config.module';
#Module({
imports: [
IamModule,
EnvConfigModule.register({
folder: 'config/environment/',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Ok, it seems I was importing wrongly. It was missing some part of the configuration. Here is the working example of CustomJwtModule:
...
import { EnvConfigModule } from 'src/utils/environment/env.config.module';
import { EnvConfigService } from 'src/utils/environment/env.config.service';
#Module({
imports: [
JwtModule.registerAsync({
imports: [EnvConfigModule.register({
folder: 'config/environment/',
}),
],
inject: [EnvConfigService],
...

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

NestJS module import not working in the forRootAsync and forRootMethod of a dynamic module

When trying to import another module inside of my dynamic modules nest throws the following error when using a service exported by that module inside of my services:
Error: Nest can't resolve dependencies of the ActivationService (UsersService, ?, USERS_MODULE_OPTIONS, MailService). Please make sure that the argument JwtService at index [1] is available in the UsersModule context.
When importing the Module with static configuration inside the #Module decorator everything works fine.
/** UsersModule */
#Global()
#Module({
imports: [
MikroOrmModule.forFeature([UserEntity]),
JwtModule.register({
secret: 'secret',
}),
],
providers: [UsersService, ActivationService, PasswordResetService],
exports: [UsersService],
controllers: [
MyUserController,
UsersController,
ActivationController,
PasswordResetController,
],
})
export class UsersModule {
/**
* Creates UsersModule asynchronously
* #param options
*/
public static forRootAsync(
options: AsyncModuleOptions<UsersModuleOptions>,
): DynamicModule {
return {
module: UsersModule,
imports: [...(options.imports || [])],
providers: createAsyncModuleOptionsProviders(
options,
USERS_MODULE_OPTIONS,
),
exports: [USERS_MODULE_OPTIONS],
};
}
}
By importing the JwtModule inside of the forRootAsync method of my dynamic module im getting the error above.
/** UsersModule */
#Global()
#Module({
imports: [MikroOrmModule.forFeature([UserEntity])],
providers: [UsersService, ActivationService, PasswordResetService],
exports: [UsersService],
controllers: [
MyUserController,
UsersController,
ActivationController,
PasswordResetController,
],
})
export class UsersModule {
/**
* Creates UsersModule asynchronously
* #param options
*/
public static forRootAsync(
options: AsyncModuleOptions<UsersModuleOptions>,
): DynamicModule {
return {
module: UsersModule,
imports: [
JwtModule.registerAsync({
inject: [USERS_MODULE_OPTIONS],
useFactory: async (options: UsersModuleOptions) => ({
secret: options.secret,
privateKey: options.privateKey,
publicKey: options.publicKey,
}),
}),
...(options.imports || []),
],
providers: createAsyncModuleOptionsProviders(
options,
USERS_MODULE_OPTIONS,
),
exports: [USERS_MODULE_OPTIONS],
};
}
}
I have already used this kind of configuration successfully in another module without obtaining the error:
/** JwtAuthenticationModule */
#Global()
#Module({
imports: [PassportModule],
providers: [
JwtAuthenticationService,
JwtStrategy,
{
provide: APP_GUARD,
useClass: JwtAuthenticationGuard,
},
],
exports: [JwtAuthenticationService],
controllers: [JwtAuthenticationController],
})
export class JwtAuthenticationModule {
/**
* Creates JwtAuthenticationModule asynchronously
* #param options
*/
public static forRootAsync(
options: AsyncModuleOptions<JwtAuthenticationModuleOptions>,
): DynamicModule {
return {
module: JwtAuthenticationModule,
imports: [
JwtModule.registerAsync({
inject: [JWT_AUTHENTICATION_MODULE_OPTIONS],
useFactory: async (options: JwtAuthenticationModuleOptions) => ({
secret: options.secret,
privateKey: options.privateKey,
publicKey: options.publicKey,
}),
}),
...(options.imports || []),
],
providers: [
this.getUsersServiceProvider(),
...createAsyncModuleOptionsProviders(
options,
JWT_AUTHENTICATION_MODULE_OPTIONS,
),
],
exports: [JWT_AUTHENTICATION_MODULE_OPTIONS],
};
}
/**
* Fetches UsersServiceProvider from options
* #private
*/
private static getUsersServiceProvider(): Provider<UsersServiceContract> {
return {
inject: [JWT_AUTHENTICATION_MODULE_OPTIONS],
provide: USERS_SERVICE,
useFactory: (options: JwtAuthenticationModuleOptions) =>
options.usersService,
};
}
}
Importing the JwtModule with a static secret inside of the forRootAsync method also throws the same error.
/** UsersModule */
#Global()
#Module({
imports: [MikroOrmModule.forFeature([UserEntity])],
providers: [UsersService, ActivationService, PasswordResetService],
exports: [UsersService],
controllers: [
MyUserController,
UsersController,
ActivationController,
PasswordResetController,
],
})
export class UsersModule {
/**
* Creates UsersModule asynchronously
* #param options
*/
public static forRootAsync(
options: AsyncModuleOptions<UsersModuleOptions>,
): DynamicModule {
return {
module: UsersModule,
imports: [
JwtModule.register({
secret: 'secret',
}),
...(options.imports || []),
],
providers: createAsyncModuleOptionsProviders(
options,
USERS_MODULE_OPTIONS,
),
exports: [USERS_MODULE_OPTIONS],
};
}
}
Try to import the AuthModule on the UsersModule

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

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 {}

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

NestJS - Use multiple MongoDB connections per module

Is there a way to connect multiple MongoDB connections per module?
app.module.ts
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/masterDB'),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }
Similarly, can we define another connection in another module which is a child of app.module?
child.module.ts
#Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/childDB'),
MongooseModule.forFeature([{ name: 'child', schema: ChildSchema }]),
],
controllers: [ChildController],
providers: [ChildService],
})
export class ChildModule { }
Or any other way to access different databases at once.
Thanks in advance!
[SOLVED March 2021]
Here you'll find the solution:
https://www.learmoreseekmore.com/2020/04/nestjs-multiple-mongodb-databases.html
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { studentSchema } from './schemas/myworld/student.schema';
import { animalSchema } from './schemas/wildlife/animal.schema';
#Module({
imports: [
MongooseModule.forFeature([
{
name: 'Student',
schema: studentSchema,
collection: 'Student',
},
],'myWorldDb'),
MongooseModule.forFeature([
{
name: 'Animals',
schema: animalSchema,
collection: 'Animals'
}
],'wildLifeDb'),
MongooseModule.forRoot(
'mongodb+srv://<userName>:<password>#cluster0-igk.mongodb.net/MyWorld?retryWrites=true&w=majority',
{
connectionName: 'myWorldDb'
}
),
MongooseModule.forRoot(
'mongodb+srv://<username>:<password>#cluster0-igk.mongodb.net/WildLife?retryWrites=true&w=majority',
{
connectionName: 'wildLifeDb'
}
)
],
controllers: [],
providers: [],
})
export class AppModule {}
You have to do it manually you have to use a providers file:
mongoose.providers.ts
import * as mongoose from 'mongoose';
export const mongooseProviders = [
{
provide: 'MASTER_CONNECTION',
useFactory: (): Promise<typeof mongoose> =>
// This mongoose.connect never working for multples DB connection
// mongoose.connect('mongodb://localhost/masterDB'),
// Following is working fine and tested by me
mongoose.createConnection('mongodb://localhost/masterDB'),
},
{
provide: 'CHILD_CONNECTION',
useFactory: (): Promise<typeof mongoose> =>
// This mongoose.connect never working for multples DB connection
// mongoose.connect('mongodb://localhost/masterDB'),
// Following is working fine and tested by me
mongoose.createConnection('mongodb://localhost/ChildDB'),
},
];
mongoose.module.ts
import { Module } from '#nestjs/common';
import { mongooseProviders } from './mongoose.providers';
#Module({
providers: [...mongooseProviders],
exports: [...mongooseProviders],
})
export class MongooseModule {}
model.providers.ts
import { Connection } from 'mongoose';
import { ChildSchema } from './schemas/child/child.schema';
import { MasterSchema } from './schemas/master/master.schema';
export const modelProviders = [
{
provide: 'CHILD_MODEL',
useFactory: (connection: Connection) => connection.model('Child', ChildSchema),
inject: ['CHILD_CONNECTION'],
},
{
provide: 'MASTER_MODEL',
useFactory: (connection: Connection) => connection.model('Master', MasterSchema),
inject: ['MASTER_CONNECTION'],
},
];
And on the constructor instead of using #InjectModel you use #Inject:
#Injectable
export Class ModelService {
constructor(#Inject('MASTER_MODEL') private masterModel: Model<Master>) {}
...
Note: in the module you provide the service you should import the MongooseModule and put as provider modelProviders.
Post above from RalphJS really helped, but it only was using one connection for everything. Had to change 1 thing in model.providers.ts:
instead of mongoose.connect
you have to use mongoose.createConnection
model.providers.ts
import * as mongoose from 'mongoose';
export const mongooseProviders = [
{
provide: 'MASTER_CONNECTION',
useFactory: async (): Promise<unknown> =>
await mongoose.createConnection('mongodb://localhost/masterDB'),
},
{
provide: 'CHILD_CONNECTION',
useFactory: async (): Promise<unknown> =>
await mongoose.createConnection('mongodb://localhost/childDB'),
},
];
https://mongoosejs.com/docs/connections.html#multiple_connections

Resources