typescriptnestjspassport.jspassport-jwt

configService in JwtStrategy constructor is undefined


I can't understand why configService is undefined in constructor of jwt.strategy.ts until I add @Inject(ConfigService) decorator before readonly configService: ConfigService, but in other modules injection works fine

Here is my AppModule:

import configuration from './modules/config/configuration';
import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { AuthModule } from './modules/auth/auth.module';

@Module({
    imports: [
        ConfigModule.forRoot({
            isGlobal: true,
            load: [configuration],
        }),
        AuthModule,
    ],
    controllers: [],
    providers: [],
})
export class AppModule {
}

main.ts

import "reflect-metadata";
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { Logger } from '@nestjs/common';

async function bootstrap() {
    const logger = new Logger('Bootstrap');
    const app = await NestFactory.create(AppModule);

    app.useLogger(logger);

    const configService = app.get(ConfigService);
    const port = configService.get<number>('port') || 4000;

    app.enableCors();

    await app.listen(port);

    logger.log(`Server is running on http://localhost:${port}`);
}

bootstrap();

auth.module.ts

import { Module } from '@nestjs/common';
import { HashService } from '../common/hash.service';
import { AuthService } from './auth.service';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtStrategy } from './strategies/jwt.strategy';

@Module({
    imports: [
        ConfigModule, 
        PassportModule,
        JwtModule.registerAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: (configService: ConfigService) => ({
                secret: configService.get<string>('jwtSecret'),
                signOptions: { expiresIn: '60m' },
            })
        })
    ],
    providers: [AuthService, HashService, LocalStrategy, JwtStrategy],
    controllers: [],
    exports: [AuthService],
})
export class AuthModule {
}

jwt.strategy.ts

import { ConfigService } from '@nestjs/config';
import { Injectable } from '@nestjs/common';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { JwtPayload } from '../jwt-payload.interface';
import { PassportStrategy } from '@nestjs/passport';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {

    constructor(
        readonly configService: ConfigService,
    ) {
        // here configService is undefined
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: configService.get<string>('jwtSecret') || '123',
        });
    }

    async validate(payload: JwtPayload) {
        return payload;
    }
}

I tried to disable almost every other module, tried to check for circular dependencies with mudge, but it didn't help


Solution

  • The issue here is that NestJS does not automatically inject dependencies into a parent class constructor. When you extend a class, the super() constructor is called before the configService is available, leading to undefined.

    Further explanation: NestJS automatically resolves dependencies without needing @Inject() because of TypeScript's metadata reflection (enabled by emitDecoratorMetadata and reflect-metadata).