I have a fairly strong MERN stack/Javascript background and I'm trying out new technos today: NestJS, which is built around TypeScrypt and OOP programmation, as I understand it.
I'm trying to build a simple API with an authentication via JSON Web Token, so I implemented a Guard
as the docs states. Note that I didn't go the full "NestJS" route with using Passport
and its strategies. I prefer implementing my own things with as less libraries as possible when discovering new technos, rather than doing a full copy-paste of a tutorial that I won't remember the next day.
I successfully wrote simple routes and wired up the project with Mongoose and MongoDB, but I stumbled on a strange error regarding hierarchy of imports/exports of NestJS modules.
The error is as follows:
[Nest] 37422 - 03/25/2022, 4:49:57 PM ERROR [ExceptionHandler] Nest can't resolve dependencies of the JwtAuthGuard (?). Please make sure that the argument ConfigService at index [0] is available in the UsersModule context.
Now I use ConfigModule
and specifically ConfigService
to have access to my environment variables. I will need it in my JwtAuthGuard
to have access to my private key and various JWT configs to create/verify tokens.
I declared my JwtAuthGuard
as follows:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { ConfigService } from '@nestjs/config';
const jwt = require('jsonwebtoken');
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private config: ConfigService) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const req = context.switchToHttp().getRequest();
// extract Authorization header and verify it here
// return true/false depending on jwt.verify return
return false;
}
createToken() {
return jwt.sign(
{ issuer: this.config.get('JWT_ISSUER') },
this.config.get('JWT_PRIVATE_KEY'),
{ algorithm: this.config.get('JWT_ALGORITHM') },
);
}
}
Of course, I import ConfigModule
in my JwtAuthModule
:
import { Module } from '@nestjs/common';
import { JwtAuthGuard } from './guard';
import { ConfigModule } from '@nestjs/config';
@Module({
exports: [JwtAuthGuard],
providers: [JwtAuthGuard],
imports: [ConfigModule],
})
export class JwtAuthModule {}
And my UsersModule
(initially) looked like so:
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './schemas/users';
import { UsersController } from './controller';
import { UsersProvider } from './service';
import { JwtAuthModule } from '../jwt-auth/module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
JwtAuthModule,
],
controllers: [UsersController],
providers: [UsersProvider],
exports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
UsersProvider,
],
})
export class UsersModule {}
I said initially because, after carefully reading the error, I fiddled around with the code and saw that when I either:
- add
ConfigModule
to the list of imports inUsersModule
, right beforeJwtAuthModule
- add
ConfigModule
to the list of exports inJwtAuthModule
the error goes away and everything seems to work fine.
The question is: Why do I have to make ConfigModule
available to UsersModule
when it, in fact, does not really need it in itself?
Edit: Forgot to add context: I use @UseGuards(JwtAuthGuard)
decorator on one of my UsersController
routes/handlers.
CodePudding user response:
From the error, I an only make an assumption that your UsersController
has @UseGuards(JwtAuthGuard)
somewhere in it. By having this, Nest will try to instantiate the JwtAuthGuard
in the context of the UsersModule
context, meaning using the dependencies it has available. As your JwtAuthGuard
uses ConfigService
, the exports of the ConfigModule
need to be avaialble, and this can be done either by having the JwtAuithModule
import and export the ConfigModule
or by having the UsersModule
import the ConfigModule
.
The reason having exports: [JwtAuthGuard]
isn't enough is kind of strange, as enhancers aren't providers in the sense that they use the same pre-built context to be built every time. They don't technically belong to a module (at least most of the time, there are exceptions that are out of the scope of this answer) so even though JwtAuthModule
does export the JwtAuthGuard
it doesn't actually mean that Nest will use that exported provider to make the instance from @UseGuards()
in the UserModule
's UserController
.
Hopefully that all makes sense