Sling Academy
Home/Node.js/NestJS: How to inject service to validator constraint class

NestJS: How to inject service to validator constraint class

Last updated: December 31, 2023

Introduction

Understanding how to leverage dependency injection in NestJS can vastly simplify the process of implementing custom validation that uses services within your application. This tutorial will guide you through the steps needed to inject services into validator constraint classes using the latest NestJS and TypeScript syntax.

Setting the Stage

Let’s start by setting up a new custom validator that would potentially require some data fetching or complex logic provided by a service. In NestJS, you can create custom validators by implementing the ValidatorConstraintInterface and using the @ValidatorConstraint decorator.

import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';

@ValidatorConstraint({ name: 'customValidator', async: false })
export class CustomValidator implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments) {
    // validation logic goes here
    return true; // or 'false' if validation failed
  }

  defaultMessage(args: ValidationArguments) {
    // a default message to be shown when validation fails
    return 'Validation failed!';
  }
}

In the code example above, we would like to inject a service into our CustomValidator. The standard dependency injection approach does not work right out of the box with custom validators. Without the correct setup, NestJS does not manage the lifecycle of validator instances, meaning that typical constructor injection will fail. However, there’s a pattern we can follow to work around this.

Injecting Services into Validator Classes

To enable service injection, we need to make our validator class a provider that can be managed by NestJS’s dependency injection system. This requires registering the custom validator as a global module so that it can be used anywhere in our application without the need to import its module everywhere.

We begin by creating a service that we want to inject:

import { Injectable } from '@nestjs/common';

@Injectable()
export class MyService {
  // Some service logic might go here
  public isValueValid(value: any): boolean {
    // Assume we have some logic to validate the value
    return true;
  }
}

Now let’s adjust our validator to allow for service injection:

import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, Injectable } from 'class-validator';
import { MyService } from './my.service';

@ValidatorConstraint({ name: 'customValidator', async: true })
@Injectable()
export class CustomValidator implements ValidatorConstraintInterface {
  constructor(private readonly myService: MyService) {}

  validate(value: any, args: ValidationArguments) {
    return this.myService.isValueValid(value);
  }

  defaultMessage(args: ValidationArguments) {
    return 'Validation failed!';
  }
}

Observe the inclusion of the @Injectable() decorator. It turns the validator into a provider. Also, note that we set the async option to true if the validation logic within the service is asynchronous.

Registering the Custom Validator

The next step is to update our application module to let NestJS know about our new injectable validator:

import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { MyService } from './my.service';
import { CustomValidator } from './custom-validator';

@Module({
  providers: [
    MyService,
    CustomValidator,
    {
      provide: APP_PIPE,
      useClass: CustomValidator
    }
  ],
})
export class AppModule {}

This module-level registration couples our custom validator with the application’s global pipes. This coupling makes it possible for NestJS to inject dependencies into the custom validator since it’s now officially part of the NestJS DI system.

Advanced Usages

In scenarios where your validation logic is more complex and spans multiple services, you might not inject a single service but rather a module. Here’s how you can achieve that by using module imports:

import { Module } from '@nestjs/common';
import { MyServiceModule } from './my-service.module';
import { CustomValidator } from './custom-validator';

@Module({
  imports: [MyServiceModule],
  providers: [CustomValidator]
})
export class ValidatorModule {}

In the above example, MyServiceModule would export MyService that the CustomValidator depends on. This modular approach maintains separation of concerns and keeps your application architecture clean and organized.

Conclusion

Injecting services into validator constraints in NestJS can make your custom validators more powerful and versatile. By turning your validators into providers with the @Injectable() decorator and appropriately registering them, you can take full advantage of the NestJS dependency injection system for cleaner and more maintainable code. Following the steps outlined in this tutorial, you can easily integrate complex validation logic that interacts with various services in your application in a seamless manner.

Next Article: How to mock a service in NestJS unit tests

Previous Article: Nest CLI Error: ‘nest’ Command Not Found in Node.js

Series: Nest.js Tutorials: From Basics to Advanced

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates