Utilizing Class Validator in NestJS: A Guide to Injecting Services into Validator Constraint Interfaces

I have been attempting to inject my users service into my validator constraint interface, but I am encountering issues with getting it 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;
    }

}

However, I am facing the issue of usersService being logged as null, preventing me from accessing its methods.

Can anyone provide insights or solutions to help resolve this problem?

Answer №1

If you are experiencing this issue, here is a solution:

To use dependencies in your custom validator constraint classes with class-validator, you need to implement service containers. More information can be found at: https://github.com/typestack/class-validator#using-service-container

import {useContainer, Validator} from "class-validator";

// Add this code at the global application level:
useContainer(Container);

Make sure to include the use container function at the global application level.

1. Insert the following code into your main.ts bootstrap function after declaring the app:

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

The {fallbackOnErrors: true} parameter is necessary as Nest will throw an Exception if the required class is not available in DI.

2. Apply 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() // Required for injecting the class 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 as a provider into your module and ensure that the service you plan to inject into the constraint is also accessible at 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 {
}

Answer №2

Just a heads up, this method doesn't seem to be effective in end-to-end testing. Here's my workaround for getting it to run smoothly.

beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
        imports: [AppModule],
    }).compile();
    app = moduleFixture.createNestApplication();

    app.useGlobalPipes(GetValidationPipe());
    useContainer(app.select(AppModule), { fallbackOnErrors: true });
    await app.init();
});

Answer №3

To enable Dependency Injection everywhere in your Nest application, it's essential to update the container of class-validator. For a detailed guide on how to do this and common issues faced by users, refer to this GitHub Issue.

If you prefer a quick solution without going through the link:

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  useContainer(app, { fallback: true });
  await app.listen(3000);
}
bootstrap();

Remember to register your validators just like any other nest @Injectable() when implementing this change.

Answer №4

My solution to the issue was as follows:

import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
import isURL from 'validator/es/lib/isURL'; // Library import for URL validation

@ValidatorConstraint({ name: 'isEmptyOrUrl' })
export class IsEmptyOrUrl implements ValidatorConstraintInterface {
  validate(value: string, validationArguments: ValidationArguments): boolean {
    // Check if the value is empty or a valid URL
    return value === '' || this.validateUrl(value);
  }

  private validateUrl(value: string): boolean {
    try {
      return isURL(value, { protocols: ['https'], require_protocol: true });
    } catch (error) {
      return false; // Returns false for invalid URL when error occurs
    }
  }
}

To implement this in the DTO file, you can do it like so:

import { IsEmptyOrUrl, IsOptional } from './path/to/validators'; // 
// Update with correct path

export class MyDto {
  @IsOptional()
  @Validate(IsEmptyOrUrl)
  readonly facebook?: string;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating definitions for generic static members within a third-party module

There is a 3rd party module with the following structure: export class Container{ static async action() { return {...} } constructor(params = {}) { // ... } async doSomething(params = {}) { // ... } } I am looking to de ...

What is the best way to distinguish shared services from other services in Angular 6 and above?

There was a time when I frequently heard the term "shared services" in relation to sharing data between unrelated components in Angular. However, I started questioning whether every service is actually classified as a shared service or not. If it is cons ...

Tips for successfully accessing a variable in an Angular Pipe

When displaying output, I need to show a specific number of decimal digits based on the length returned by a stored procedure. The length of these decimal digits can vary, and I only need to focus on the decimal part, not the integer part. To achieve this, ...

Verifying the outcomes of a spied function with the callThrough method

Is there a way to validate the outcomes of a spied function in nestjs using jasmine? I have set up a jasmine spy on a method to monitor its arguments and response/exception, but I'm unable to access the return value of the spied method. For example, ...

Typescript: Potential null object error when defining a method

I recently encountered an error message stating "Object is possibly null" while working on the changePageSize method in book-store.component.html. It seems like I need to initialize the object within the class, but I'm not familiar with how to do that ...

Connect a property of a class instance to a chain of extended class types. Instruct TypeScript to permit the behavior of JavaScript's "this"

Check out this code snippet: class Parent { constructor() { } // this method is called from child, // so 'this' is type of Child and cosmisFunc exists callChildFunction() { this.cosmisFunc() // compiler error ...

Arrange information into sections

This Angular code is used to format text into profile page as blocks of data: <div class="element-box"> <div class="details-wrapper"> <p><b class="label">Remote IP</b>{{apiattempt.remote_ip}}</p> <p>< ...

The directive [ngTemplateOutet] is functioning properly, however, the directive *ngTemplateOutlet is not functioning as expected

I have been struggling to create a component using ngTemplateOutlet to select an ng-template, but for some reason *ngTemplateOutlet is not working in this case (although [ngTemplateOutlet] is functioning correctly). Below is the code I am currently using: ...

What is the best way for me to generate a fresh object?

In one of my components, I have implemented a feature where clicking on an image toggles a boolean variable to show or hide a menu. The HTML structure for this functionality is as follows: <img src="../../assets/image/dropdown.png" class="dropdown-imag ...

Is there a way to store my Typeorm data source configuration in a separate variable?

I am currently working with NestJS 10 and TypeORM 0.3.17. In my src/config/data-source.ts file, I have the following code snippet... import * as dotenv from 'dotenv'; import * as dotenvExpand from 'dotenv-expand'; import { DataSource } ...

Exploring the realm of unit testing in the NestJS CQRS architecture journey

We're currently working on writing unit tests using Jest for our application and are facing difficulties in testing sagas. Specifically, we're having trouble testing the saga itself. During our unit testing process, we've encountered an iss ...

Is there a way in Typescript to filter for the first instance of a unique object in an array of objects?

Having an array of JSON objects like this, the task is to iterate through it and retrieve the first occurrence of 'appname', such as 'app1' or 'app2', and store the entire object for each... myArray[ { ...

Error occurs in Typescript when attempting to store data in a record using a pointer

When working with nodes and organizing them into a tree structure, I encounter an issue: This is the definition of the interface: interface IDataObj { children: IDataObj[], frontmatter : { type: string, title: string, path: string}, name: str ...

Vue3 project encountering issues with Typescript integration

When I created a new application using Vue CLI (Vue3, Babel, Typescript), I encountered an issue where the 'config' object on the main app object returned from the createApp function was not accessible. In VS Code, I could see the Typescript &ap ...

Ensuring ES module exports are statically type checked in TypeScript

Can TypeScript handle static type checking for ES module exports in the following code snippet? // file.ts export const x = 4 export const y = 'whatever' export const foo = () => 2 // The interface describes what the module exports interf ...

Typing a general d3 selection to target various types of SVG elements

I am looking for a callback function that can be utilized with both rect and circle elements. Here is an example: function commonTasks(selection:d3.Selection<PLACEHOLDER_TYPE, MyDataType, SVGGElement, unknown>) { selection .classed('my-c ...

What is the command to determine the version of TypeScript being used in the command line interface (CLI)?

I recently added TypeScript 1.7.4 using Visual Studio 2015, and it appears correctly installed within the IDE. However, when I check the version using tsc --version in the command line, it shows a different, older version - 1.0.3.0 instead of 1.7.4. Is t ...

Enhance the API response for Angular service purposes

I am currently working on modifying the response returned by an API request. At the moment, I receive the response as: [ { name: "Afghanistan" }, { name: "Åland Islands" } ] My goal is to adjust it to: [ { name: "A ...

Throwing in a ternary/conditional operator expression in Typescript 2

When attempting to compile the following code: const value = "20" const x : string | never = "10" === value ? throw Error("bad things") : "hello" An error is encountered on throw - expression expected. One way to resolve this issue is by using an inline ...

Utilizing Angular 4 with Ahead-Of-Time compilation in combination with Electron, as well as

I am new to Angular/Typescript and currently developing a cross-platform Desktop application with Electron and Angular 4. My current challenge involves using a Service in different components, but I need this service to be loaded from a separate file based ...