Search code examples
node.jstypescriptnestjsclass-validatornestjs-config

NestJS Service/Validator throws TypeError: Cannot read properties of undefined (reading 'get') Any of the injected objects are not available


I have the following NestJS Validator Service

@ValidatorConstraint({ async: true })
@Injectable()
export class IsValidWorkingHourConstraint implements ValidatorConstraintInterface {
  private readonly WORKING_HOUR_START: number;
  private readonly WORKING_HOUR_END: number;
  constructor(protected readonly helperDateService: HelperDateService, private readonly configService: ConfigService) {
    this.WORKING_HOUR_START = this.configService.get<number>('workStartHour');
                                             // ^^^^^this thrown the error
    this.WORKING_HOUR_END = this.configService.get<number>('workEndHour');
  }
  validate(value: string | number | Date) {
    const date = new Date(value);
    const day = date.getDay(); // 0 (Sunday) to 6 (Saturday)
    const hours = date.getHours(); // 0 to 23

    if (!this.helperDateService.isWorkingDay(day)) {
      return false;
    }

    if (hours < this.WORKING_HOUR_START || hours >= this.WORKING_HOUR_END) {
      return false;
    }

    return true;
  }
}

As you can see, ConfigService is Injected, but the error says it is undefined.

This service is provided in the below Module.

@Module({
  controllers: [],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: RequestTimeoutInterceptor,
    },
    {
      provide: APP_PIPE,
      useFactory: () =>
        new ValidationPipe({
          transform: true,
          skipNullProperties: false,
          skipUndefinedProperties: false,
          skipMissingProperties: false,
          forbidUnknownValues: false,
          errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
          exceptionFactory: async (errors: ValidationError[]) =>
            new UnprocessableEntityException({
              statusCode: ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR,
              message: 'request.validation',
              errors,
            }),
        }),
    },
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
    IsValidWorkingHourConstraint,
  ],
  imports: [
    RequestMiddlewareModule,
    ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        ttl: config.get('request.throttle.ttl'),
        limit: config.get('request.throttle.limit'),
      }),
    }),
  ],
})
export class RequestModule {}

And the following message and stack trace are thrown:

TypeError: Cannot read properties of undefined (reading 'get')
      at new IsValidWorkingHourConstraint (A:\DEV\HR\deadline-calculator\src\common\request\validations\request.is-valid-working-hour.validation.ts:17:50)
      at Object.get (A:\DEV\HR\deadline-calculator\node_modules\src\container.ts:25:45)
      at getFromContainer (A:\DEV\HR\deadline-calculator\node_modules\src\container.ts:58:27)
      at ConstraintMetadata.get instance [as instance] (A:\DEV\HR\deadline-calculator\node_modules\src\metadata\ConstraintMetadata.ts:45:28)
      at A:\DEV\HR\deadline-calculator\node_modules\src\validation\ValidationExecutor.ts:271:59
      at Array.forEach (<anonymous>)
      at A:\DEV\HR\deadline-calculator\node_modules\src\validation\ValidationExecutor.ts:253:82
      at Array.forEach (<anonymous>)
      at ValidationExecutor.customValidations (A:\DEV\HR\deadline-calculator\node_modules\src\validation\ValidationExecutor.ts:252:15)
      at ValidationExecutor.performValidations (A:\DEV\HR\deadline-calculator\node_modules\src\validation\ValidationExecutor.ts:212:10),

I tried to add ConfigModule and ConfigService to RequestModule, but neither helped.

I also tried to inject the service and assign it as shown below:

@Inject(ConfigService)
private readonly configService: ConfigService

constructor (configService: ConfigService) {
  this.service = service
}

EDIT: The HelperDateService is also not working in this service.


Solution

  • You cannot auto-inject dependencies into @ValidatorConstraint using NestJS.

    You can instead do a workaround in your main.ts file.

    import { useContainer } from 'class-validator';
    
    import { ApplicationModule } from './app.module';
    
    async function bootstrap() {
      const app = await NestFactory.create(ApplicationModule);
      useContainer(app.select(ApplicationModule), { fallbackOnErrors: true });
      await app.listen(3000);
    }
    
    bootstrap()
    

    By using the above workaround, class-validator will be using the module resolution from NestJS, and the dependency injection in @ValidatorConstraint should work now.