NestJS can't resolve dependencies of class that is provided with a factory - nestjs

Let me introduce you the AlgoModule:
const apiFactory = {
provide: 'API',
useFactory: (moduleRef: ModuleRef, config: ConfigService) => {
const apiService: Type<IApi> = config.getApiService();
return moduleRef.get(apiService)
},
inject: [ModuleRef, ConfigService]
}
#Module({
imports: [ConfigModule],
providers: [
// Services
ApiService,
ApiTestService,
TestService,
// Factories
apiFactory
],
exports: []
})
export class AlgoModule {}
This module imports the ConfigModule and has a factory that creates an IApi instance which is either ApiService or ApiTestService based on env parameters that ConfigService under ConfigModule has loaded.
The factory uses the ConfigService injected onto it and the ModuleRef in order to create an instance of IApi.
This is an example of one of the implementations of IApi - ApiService:
#Injectable()
export class ApiService implements IApi {
constructor(private config: ConfigService) {
}
}
Now when I create a new service and put it in the provides of AlgoModule, let's say TestService:
#Injectable()
export class TestService {
constructor(
private config: ConfigService,
#Inject('API') private api: IApi) {
}
}
What I did here is, inject IApi with the token 'API' which is what I set up in AlgoModule using the apiFactory
I will get the following error upon start:
Error: Nest can't resolve dependencies of the ApiService(?). Please make sure that the argument dependency at index [0] is available in the AlgoModule context.
What did I do wrong?
I am wondering, because I imported the ConfigModule within AlgoModule so the factory should automatically have the dependencies of it.
ConfigService:
#Injectable()
export class ConfigService {
serviceMapping = {
prod: ApiService,
test: ApiTestService
};
getEnv(): string | 'prod' | 'test' {
return String(process.env.ENV);
}
getApiService(): Type<IApi> {
const env = this.getEnv();
return this.serviceMapping[env];
}
}

Did you call ConfigModule.forRoot with flag isGlobal somewhere in global modules?
If not then you must because ConfigModule does not work as you imagine.
ConfigModule.forRoot or forFeature exposes ConfigService token for module but plain ConfigModule do not unless it is global.

Related

NestJS share dynamic module instance

I'm struggling a bit with Nest's dynamic modules.
I have a utility service which needs a password to be injected on startup.
#Injectable()
export class CryptoService {
constructor(
#Inject("password") private password: string
) {}
...
}
This service is part of a "shared" CryptoModule...
#Module({
providers: [CryptoService]
,exports: [CryptoService]
})
export class CryptoModule {
public static forRoot(password: string): DynamicModule {
const providers = [{
provide: "password"
,useValue: password
}];
return {
module: CryptoModule
,providers: providers
};
}
}
With "shared" I mean that it's not specific to the current app, so it can be reused in other Nest apps as well.
I import the CryptoModule in my AppModule like so...
#Module({
imports: [
...
,CryptoModule.forRoot("super-secure-password")
,EmailModule
]
,controllers: [...]
,providers: [...]
,exports: [CryptoModule]
})
export class AppModule {
...
}
This works and I can inject the CryptoService in my AppModule classes/services/controllers.
Now I have a 3rd Module (EmailModule) which should be independent from my app, so I can reuse it in other Nest apps as well.
Question now is, how do I import the CryptoModule in the EmailModule without having to set the password in the EmailModule? It should use the password that was passed to the CryptoModule in AppModule.
I tried importing CryptoModule like so...
#Module({
imports: [
...
,CryptoModule
]
,providers: [EmailService]
,exports: [EmailService]
})
export class EmailModule {
...
}
This code however triggers an exception on startup saying:
Nest can't resolve dependencies of the CryptoService...
If password is a provider, is it part of the current CryptoModule?
Is there a best practice of how to achieve this? I could make EmailModule dynamic as well and pass in the password, so it can forward it to the CryptoModule, but somehow that doesn't feel right.
Thanks!
I ended up doing the following (probably there are better ways of doing this)...
Added a static forChildren() function to the CryptoModule, which is used to initialize the module when being imported by the EmailModule (or any other module). The forRoot() method stores the password in an Observable, which is used by forChildren() to asynchronously retrieve the password. Doing this async is required to defeat the race condition between forChildren() and forRoot().
import { firstValueFrom, ReplaySubject } from 'rxjs';
...
#Module({
providers: [CryptoService]
,exports: [CryptoService]
})
export class CryptoModule {
private static readonly credentialsResolved = new ReplaySubject<string>(1);
public static forRoot(password: string): DynamicModule {
CryptoModule.credentialsResolved.next(password);
const providers = [{
provide: "password"
,useValue: password
}];
return {
module: CryptoModule
,providers: providers
};
}
public static forChildren(): DynamicModule {
const providers = [{
provide: "password"
,useFactory: async () => await firstValueFrom(CryptoModule.credentialsResolved)
}];
return {
module: CryptoModule
,providers: providers
};
}
}
Usage in EmailModule:
#Module({
imports: [
...
,CryptoModule.forChildren()
]
,providers: [EmailService]
,exports: [EmailService]
})
export class EmailModule {
...
}

Cannot inject custom provider for dynamic module

I tried to create a dynamic module PermissionModule like the following:
permTestApp.module.ts
#Module({
imports: [PermissionModule.forRoot({ text: 'abc' })],
providers: [],
controllers: [PermissionTestController],
})
export class PermissionTestAppModule {}
permission.module.ts
import { DynamicModule, Module } from '#nestjs/common'
import { PermissionGuard } from './guard/permission.guard'
#Module({})
export class PermissionModule {
public static forRoot(config: { text: string }): DynamicModule {
return {
module: PermissionModule,
providers: [
{
provide: 'xoxo',
useValue: config.text,
},
PermissionGuard,
],
exports: [PermissionGuard],
}
}
}
permission.guard.ts
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
} from '#nestjs/common'
#Injectable()
export class PermissionGuard implements CanActivate {
constructor(#Inject('xoxo') private readonly config: string) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
console.log(31, this.config)
return true
}
}
AFAIK, 'abc' string must be injected when PermissionGuard is used.
I tried to test it with the following code.
permission.e2e.spec.ts
beforeAll(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
imports: [PermissionTestAppModule],
})
.compile()
app = moduleRef.createNestApplication()
controller = await moduleRef.resolve(PermissionTestController)
await app.init()
})
but it says,
Nest can't resolve dependencies of the PermissionGuard (?). Please make sure that the argument xoxo at index [0] is available in the PermissionTestAppModule context.
Potential solutions:
- Is PermissionTestAppModule a valid NestJS module?
- If xoxo is a provider, is it part of the current PermissionTestAppModule?
- If xoxo is exported from a separate #Module, is that module imported within PermissionTestAppModule?
#Module({
imports: [ /* the Module containing xoxo */ ]
})
What am I doing wrong?

NestJS test cannot resolve PinoLogger dependency

On NestJS application startup I am creating few db tables (using TypeORM) and have written service file init-db.service.ts for the same. The service file implements OnApplicationBootstrap interface and onApplicationBootstrap method is called once the application has fully started and is bootstrapped.
I am trying to write a unit test for the service file, but is unable to resolve PinoLogger dependency while creating Testing Module. Please checkout below sample code:
Service file init-db.service.ts:
import {ConfigService} from '#nestjs/config';
import {PinoLogger} from 'nestjs-pino';
import {Injectable, OnApplicationBootstrap} from '#nestjs/common';
#Injectable()
export class InitDB implements OnApplicationBootstrap {
constructor(private readonly logger: PinoLogger, private readonly configService: ConfigService) {}
onApplicationBootstrap() {
this.someMethod();
}
async someMethod(): Promise<void> {
// code to create tables...
}
}
Unit test file init-db.service.spec.ts:
import {ConfigService} from '#nestjs/config';
import {Test, TestingModule} from '#nestjs/testing';
import {InitDB} from './init-db.service';
import {getLoggerToken, PinoLogger} from 'nestjs-pino';
const mockLogger = {
// mock logger functions
};
describe('InitDB', () => {
jest.useFakeTimers();
let configService: ConfigService;
let service: InitDB;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getLoggerToken(InitDB.name), // <-------- PinoLogger dependency
useValue: mockLogger,
},
InitDB,
ConfigService,
],
}).compile();
service = module.get<InitDB>(InitDB);
configService = module.get<ConfigService>(ConfigService);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
Error Message:
Nest can't resolve dependencies of the BootstrapService (?, ConfigService). Please make sure that the argument PinoLogger at index [0] is available in the RootTestModule context.
Potential solutions:
- If PinoLogger is a provider, is it part of the current RootTestModule?
- If PinoLogger is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing PinoLogger */ ]
})
I have already passed PinoLogger at index [0], but the unit test still fails to resolve the dependency.
Please note that you don't use #InjectPinoLogger but you are trying to mock it as if you have used it.
You could either provide PinoLogger as a dependency:
const module: TestingModule = await Test.createTestingModule({
providers: [
PinoLogger,
InitDB,
ConfigService,
],
}).compile();
or use #InjectPinoLogger in your class constructor:
export class InitDB implements OnApplicationBootstrap {
constructor(
#InjectPinoLogger(InitDB.name)
private readonly logger: PinoLogger,
private readonly configService: ConfigService
) {}
}

NestJS - How to register dynamic module provider multiple times using different configuration?

I have a knex module which is implemented like this:
import { DynamicModule, Module } from '#nestjs/common';
import { Knex, knex } from 'knex';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
export const KNEX_MODULE = 'KNEX_MODULE';
#Module({})
export class KnexModule {
static register(options: Knex.Config): DynamicModule {
return {
module: KnexModule,
providers: [
{
inject: [WINSTON_MODULE_PROVIDER],
provide: KNEX_MODULE,
useFactory: (logger: Logger) => {
logger.info('Creating new knex instance', {
context: KnexModule.name,
tags: ['instance', 'knex', 'create'],
});
return knex(options);
},
},
],
exports: [KNEX_MODULE],
};
}
}
My application requires access to multiple databases, I know I can do that by creating multiple knex instances. So I tried to register the module twice, passing different configurations. However, the module only registered once. The second register call seems to be reusing the existing object instead of creating a new knex instance.
What is the correct way to generate multiple providers, depending on the configuration passed? The closest thing I found is the forFeature functions in typeORM and Sequelize
I just found the solution. I was thinking the wrong way. I needed to register two providers to my Module. Not create two instances of my module. I solved it by adding one more parameter to my module, which is the provider token. Now it correctly creates the two providers.
import { DynamicModule, Module } from '#nestjs/common';
import { Knex, knex } from 'knex';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
export const KNEX_MODULE = 'KNEX_MODULE';
#Module({})
export class KnexModule {
static register(token: string, options: Knex.Config): DynamicModule {
return {
module: KnexModule,
providers: [
{
inject: [WINSTON_MODULE_PROVIDER],
provide: token,
useFactory: (logger: Logger) => {
logger.info('Creating new knex instance', {
context: KnexModule.name,
tags: ['instance', 'knex', 'create'],
});
return knex(options);
},
},
],
exports: [token],
};
}
}
And whenever I want to use it I register it like this:
#Module({
imports: [KnexModule.register(CatRepository.KNEX_TOKEN, knexConfigs)],
providers: [CatRepository, CatService],
controllers: [CatController],
exports: [CatService],
})
export class CatModule {}
Then in the repository I can inject the knex instance of the cats database.
#Injectable()
export class CatRepository implements Repository<Cat> {
// eslint-disable-next-line no-useless-constructor
public static KNEX_TOKEN = 'KNEX_CATS_TOKEN';
// eslint-disable-next-line no-useless-constructor
constructor(
#Inject(CatRepository.KNEX_TOKEN)
protected knex: Knex,
) {}
...
}

Nest can't resolve dependencies of the VendorsService (?). Please verify whether [0] argument is available in thecurrent context

I am new to nest js and typescript also. Thanks in advance.
I am getting this error continuously.
Nest can't resolve dependencies of the VendorsService (?). Please verify whether [0] argument is available in thecurrent context.
Here is the code
App module
#Module({
imports: [ UsersModule, VendorsModule],
})
export class ApplicationModule {}
controller
#Controller()
export class VendorsController {
constructor(private readonly vendorService: VendorsService){}
#Post()
async create(#Body() createVendorDTO: CreateVendorDTO){
this.vendorService.create(createVendorDTO);
}
#Get()
async findAll(): Promise<Vendor[]>{
return this.vendorService.findAll();
}
}
Service
#Injectable()
export class VendorsService {
constructor(#Inject('VendorModelToken') private readonly vendorModel: Model<Vendor>) {}
async create(createVendorDTO: CreateVendorDTO): Promise<Vendor>{
const createdVendor = new this.vendorModel(createVendorDTO);
return await createdVendor.save();
}
async findAll(): Promise<Vendor[]>{
return await this.vendorModel.find().exec();
}
}
provider
export const usersProviders = [
{
provide: 'VendorModelToken',
useFactory: (connection: Connection) => connection.model('Vendor', VendorSchema),
inject: ['DbConnectionToken'],
},
];
Module
#Module({
providers: [VendorsService],
controllers: [VendorsController],
})
export class VendorsModule {}
VendorsModule sould declare your provider (usersProviders) in its providers, otherwise Nestjs will never be able to inject it into your service.
Unless you wanted to declare it with UsersModule (I guess you did); in that case, UsersModule also needs it in its exports so it's made visible to other modules importing UsersModule.
It's either VendorsModule: usersProviders in providers,
Or UsersModule: usersProviders in both providers and exports

Resources