How to resolve DI issue in nest.js? - node.js

I wanna understand how to import 3rd party library in nestjs through DI. So, i have a class AuthService:
export class AuthService {
constructor(
#Inject(constants.JWT) private jsonWebToken: any,
){}
....
}
JWT provider:
import * as jwt from 'jsonwebtoken';
import {Module} from '#nestjs/common';
import constants from '../../../constants';
const jwtProvider = {
provide: constants.JWT,
useValue: jwt,
};
#Module({
components: [jwtProvider],
})
export class JWTProvider {}
Libraries module:
import { Module } from '#nestjs/common';
import {BcryptProvider} from './bcrypt/bcrypt.provider';
import {JWTProvider} from './jsonwebtoken/jwt.provider';
#Module({
components: [
BcryptProvider,
JWTProvider,
],
controllers: [],
exports: [
BcryptProvider,
JWTProvider,
],
})
export class LibrariesModule{
}
I'm getting this error:
Error: Nest can't resolve dependencies of the AuthService (?). Please verify whether [0] argument is available in the current context.
at Injector.<anonymous> (D:\Learning\nest\project\node_modules\#nestjs\core\injector\injector.js:156:23)
at Generator.next (<anonymous>)
at fulfilled (D:\Learning\nest\project\node_modules\#nestjs\core\injector\injector.js:4:58)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
Besides, I wanna hear some recommendations about not using type any in the jsonWebToken variable.

The devil is in details. You can "import" other module into AuthModule like so:
#Module({
modules: [LibrariesModule], // <= added this line
components: [AuthService, JwtStrategy],
controllers: [],
})
export class AuthModule {
}
Source: here
Second question is still opened.

Related

Nest can't resolve DataSource as dependency in my custom module

I've had a problem with dependencies in NestJS. On launch my NestJS app, compiler throw me this:
Nest can't resolve dependencies of the OtpRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.
OtpModule.ts
#Global()
#Module({
imports: [TypeOrmModule.forFeature([Otp])],
providers: [
OtpService,
OtpRepository,
],
exports: [TypeOrmModule, OtpService, OtpRepository],
})
export class OtpModule {}
OtpService.ts
#Injectable()
export class OtpService {
constructor(private readonly otpRepository: OtpRepository) {}
OtpRepository.ts
#Injectable()
export class OtpRepository extends Repository<Otp> {
constructor(#InjectDataSource() private dataSource: DataSource) {
super(Otp, dataSource.createEntityManager());
}
UsersModule.ts
#Module({
imports: [OtpModule],
controllers: [],
providers: [],
})
export class UsersModule {}
When exporting the otp module as npm package got this error but if included the OtpModule within the project working fine
App compile successully

NestJS : TypeError: Cannot read property 'get' of undefined

I am trying to pass the connection parameters to mongodb by enviroment file and I am getting the following error
[Nest] 1176 - 12/10/2021 23:34:35 ERROR [ExceptionHandler] Cannot read property 'get' of undefined
TypeError: Cannot read property 'get' of undefined
at MongoService.createMongooseOptions (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/src/common/mongo/mongo.service.ts:20:41)
at Function.<anonymous> (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:135:120)
at Generator.next (<anonymous>)
at /Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:20:71
at new Promise (<anonymous>)
at __awaiter (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:16:12)
at InstanceWrapper.useFactory [as metatype] (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/mongoose/dist/mongoose-core.module.js:135:45)
at Injector.instantiateClass (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:294:55)
at callback (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:43:41)
at Injector.resolveConstructorParams (/Users/Desarrollos/NestJS/nestjs-ventas-negocios/node_modules/#nestjs/core/injector/injector.js:119:24)
This is the service mongo.service.ts
import { MongooseModuleOptions, MongooseOptionsFactory } from '#nestjs/mongoose';
import { Configuration } from '../../config/config.keys';
import { ConfigService } from '../../config/config.service';
export class MongoService implements MongooseOptionsFactory {
constructor( private configService: ConfigService ) {}
createMongooseOptions(): MongooseModuleOptions {
const user = this.configService.get(Configuration.DB_MONGO_USER);
const password = this.configService.get(Configuration.DB_MONGO_PASSWORD);
const server = this.configService.get(Configuration.DB_MONGO_HOST);
const database = this.configService.get(Configuration.DB_MONGO_DATABASE);
return {
uri: `mongodb://${user}:${password}#${server}/${database}?retryWrites=true&w=majority`,
};
}
}
And this I import it in the app.module.ts
#Module({
imports: [
MongooseModule.forRootAsync({
useClass: MongoService,
}),
Any suggestion,
thanks,
JM
You need 2 things here:
Your MongoService needs to be marked with #Injectable() so that Nest can read the metadata of the constructor and set up the injection properly
If your ConfigModule does not have a globally exported ConfigService, then in MongooseModule.forRootAsync() along with the useClass you need to have imports: [ConfigModule] so that you can inject the ConfigService
#Module({
imports: [
MongooseModule.forRootAsync({
imports: [ ConfigModule ],
inject: [ConfigService],
useClass: MongoService
})
})

NestJs - ConfigModule.forRoot isGlobal not working

I am trying to load the "process.env.AUTH_SECRET" in AuthModule, but it is giving me the undefined error "Error: secretOrPrivateKey must have a value".
I did setup the "isGlobal: true" in AppModule and it was able to read "process.env.MONGO_URL" there fine.
I have also installed dotenv, which reads fine if I do:
import * as dotenv from 'dotenv';
dotenv.config();
export const jwtConstants = {
secret: process.env.AUTH_SECRET,
};
But I would rather do it the "NestJs" way as the doc says adding isGlobal should make the env available to all other modules.
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from '../user/user.module';
import { PassportModule } from '#nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [
UserModule,
PassportModule,
JwtModule.register({
secret: process.env.AUTH_SECRET, //Cannot read this.
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, LocalStrategy],
exports: [AuthService, JwtModule],
})
export class AuthModule {}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { MongooseModule } from '#nestjs/mongoose';
import { ConfigModule } from '#nestjs/config';
import { AuthModule } from './auth/auth.module';
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
UserModule,
MongooseModule.forRoot(process.env.MONGO_URL), // Can read this fine
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
What am I missing or doing wrong? Just for reference, I am trying to follow this authentication tutorial.
Thank you,
Your MongooseModule and AuthModule are dependent on your ConfigModule, but they don't know it.
Your modules are busy being loaded up, however your ConfigModule has to preform an async i/o process (reading your .env) before it can be ready. Meanwhile, Nest carries on loading without waiting for ConfigModule to finish it's job unless another it finds another module is dependent on one of it's exports.
This is where a modules *Async flavoured methods come in to play. They give you control over the modules instantiation. In this context, they allow us to inject the ConfigService from the ConfigModule which will not happen until the ConfigService is ready.
So changing your JWTModule configuration from using .register to .registerAsync to inject the ConfigService is what you are after:
JWTModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
secret: config.get<string>('AUTH_SECRET'),
signOptions: { expiresIn: '60s' }
}
})
Now, JWTModule will not load until ConfigService is ready and available in scope. You will most likely need to do that for your MongooseModule too.
That is the "NestJS" way.
Saying that, if all you really needed to do was have your .env loaded into process.env, put:
import * as dotenv from 'dotenv';
import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '../.env') });
At the start of your main.ts file. That would lead to dotenv synchronously loading it before anything else happened

exporting nest js middleware to another project

I'm trying to export a custom middleware project in NestJS to all my other projects(and import this in all of them). My actual class is acl-jwt.middleware.ts in its bootstrap src folder.
In the acl-jwt.middleware.ts I have:
import { Injectable, NestMiddleware, } from '#nestjs/common';
#Injectable()
export class AclJwtMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log("ACL JWT Middleware !!")
next();
}
}
and my app.module.ts has:
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AclJwtMiddleware } from './acl-jwt.middleware';
#Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AclJwtMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL });
}
}
And in my another project's app.module.ts, I'm importing this like the following
import { AclJwtMiddleware } from 'mi';
#Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AclJwtMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL });
}
}
mi is my hosted package.
And then when I run my second project, I get the following error:
src/app.module.ts:16:34 - error TS2307: Cannot find module 'mi' or its corresponding type declarations.
16 import { AclJwtMiddleware } from 'mi';
Am I not exporting something? Or am I not importing it properly?
I've been searching all over the internet but I couldn't find something with my use case. Any help would be really good. Thank you all! :)
You'd need to make sure that in your package.json of your mi package, you have either main or files that points to the properly exported file/directory. If it is a barrel file, like index.js, then you need to make sure it exports the middleware properly.

Can't use custom provider

I'm trying to inject a custom provider as per the documentation:
https://docs.nestjs.com/fundamentals/custom-providers
My service:
#Injectable({scope: Scope.REQUEST})
export class ReportService implements OnModuleInit {
private readonly reportRepository: Repository<Report>;
constructor(
public config: ConfigService,
private readonly moduleRef: ModuleRef,
#Inject('CONNECTION') connection: Connection,
) {
console.log(connection);
}
...
app.module.ts:
const connectionProvider = {
provide: 'CONNECTION',
useValue: connection,
};
#Module({
imports: [
ReportModule,
...
],
providers: [connectionProvider],
controllers: [AppController],
})
export class AppModule implements NestModule {
Doing so results in:
Error: Nest can't resolve dependencies of the ReportService (ConfigService, ModuleRef, ?). Please make sure that the argument at index [2] is available in the ReportModule context.
What am I missing?
If a provider should be available outside of the module you defined it in, you need to add it to exports in the module definition (app.module). The other module (report.module) using it needs to add the module to its imports definition.
app.module.ts
const connectionProvider = {
provide: 'CONNECTION',
useValue: Connection,
};
#Module({
imports: [
ReportModule,
...
],
providers: [connectionProvider],
exports: ['CONNECTION'],
controllers: [AppController],
})
export class AppModule implements NestModule {}
report.module.ts
#Module({
imports: [
AppModule
],
providers: [],
controllers: [],
})
export class ReportModule implements NestModule {}
In your case, this produces a circular dependency which needs to be resolved.
Since app.module seems to be your core module you could make it global but you still need to export the provider.
Alternatively, I found it to be a good practice to not define any providers in app.module and use DynamicModule (e.g. forRoot and forFeature static initiator functions) to only instantiate what is needed, but that seem to be outside of this question.

Resources