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
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
Share Improve this question asked Mar 16 at 14:34 Тимофей МелентьевТимофей Мелентьев 11 bronze badge2 Answers
Reset to default 1The 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 forConfigService
in the subclass constructor. NestJS can't inferConfigService
fromreadonly configService: ConfigService
because thesuper()
call is required first. SinceConfigService
is injected before the call tosuper()
, 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.
In my case there was a problem: my tsconfig.json was missing emitDecoratorMetadata: true