Search code examples
typescriptnestjsclass-validator

How to inject service to validator constraint interface in nestjs using class-validator?


I'm trying to inject my users service into my validator constraint interface but it doesn't seem to work:

import { ValidatorConstraintInterface, ValidatorConstraint, ValidationArguments, registerDecorator, ValidationOptions } from "class-validator";
import { UsersService } from './users.service';

@ValidatorConstraint({ async: true })
export class IsEmailAlreadyInUseConstraint implements ValidatorConstraintInterface {
    constructor(private usersService: UsersService) {
        console.log(this.usersService);
    }
    validate(email: any, args: ValidationArguments) {
        return this.usersService.findUserByEmail(email).then(user => {
             if (user) return false;
             return true;
        });
        return false;
    }

}

But, as usersService is logged null, I can't access its methods.

Any insight on this matter?


Solution

  • For those who might be suffering from this issue:

    class-validator requires you to use service containers if you want to inject dependencies into your custom validator constraint classes. From: https://github.com/typestack/class-validator#using-service-container

    import {useContainer, Validator} from "class-validator";
    
    // do this somewhere in the global application level:
    useContainer(Container);
    

    So that we need to add the user container function into the global application level.

    1. Add the following code to your main.ts bootstrap function after app declaration:

    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      useContainer(app.select(AppModule), { fallbackOnErrors: true });
    ...}
    

    The {fallbackOnErrors: true} is required, because Nest throw Exception when DI doesn't have required class.

    2. Add Injectable() to your constraint:

    import {ValidatorConstraint, ValidatorConstraintInterface} from 'class-validator';
    import {UsersService} from './user.service';
    import {Injectable} from '@nestjs/common';
    
    @ValidatorConstraint({ name: 'isUserAlreadyExist', async: true })
    @Injectable() // this is needed in order to the class be injected into the module
    export class IsUserAlreadyExist implements ValidatorConstraintInterface {
        constructor(protected readonly usersService: UsersService) {}
    
        async validate(text: string) {
            const user = await this.usersService.findOne({
                email: text
            });
            return !user;
        }
    }
    

    3. Inject the constraint into your module as a provider and make sure that the service you intend to inject into your constraint are also available to a module level:

    import {Module} from '@nestjs/common';
    import { UsersController } from './user.controller';
    import { UsersService } from './user.service';
    import { IsUserAlreadyExist } from './user.validator';
    
    @Module({
        controllers: [UsersController],
        providers: [IsUserAlreadyExist, UsersService],
        imports: [],
        exports: []
    })
    export class UserModule {
    }