[SOLVED] Resolving dependencies with NestJS

Issue

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 in UsersModule, right before JwtAuthModule
  • add ConfigModule to the list of exports in JwtAuthModule

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.

Solution

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

Answered By – Jay McDoniel

Answer Checked By – Timothy Miller (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *