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
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).
When you extend the class PassportStrategy
, TypeScript loses the type metadata for ConfigService
in the subclass constructor.
NestJS can't infer ConfigService
from readonly configService: ConfigService
because the super()
call is required first.
Since ConfigService
is injected before the call to super()
, the metadata isn't available at that point.
@Inject(ConfigService)
explicitly tells NestJS:
Ignore the missing metadata, manually provide the correct dependency and force NestJS to resolve ConfigService properly.