Understanding Data Transfer Objects (DTOs) and Validators in NestJS.

  • What are DTOs?

  • What are Validators?

DTOs:

In NestJS, DTOs are typically defined as TypeScript classes or interfaces, and are used to define the shape of the data that is sent between different layers of an application. For example, when building a REST API, DTOs can be used to define the shape of the data that is sent from the client to the server, or from the server to the client. This helps to ensure that the data is properly formatted and validated, and can help to prevent security issues such as injection attacks.

By default, A normal DTO doesn't necessarily have validation inside, it's just a data transfer object that defines the shape of the data being transferred between different layers of an application. However, in practice, it's often a good idea to include validation inside a DTO to ensure that the data being transferred is valid and meets certain criteria. This can help to prevent errors and improve the overall security of an application.

Here is how a default D.T.O looks like

export class CreateUserDto {

    username: string;

    email: string;

    age: number;
}

However, it's worth noting that this DTO doesn't have any validation rules defined for its properties. Depending on the requirements of your application, you may want to add validation rules to ensure that the data being transferred is valid and meets certain criteria which brings us to what are validator

Validators:

Validators, on the other hand, are used to ensure that data meets specific criteria before it is processed. In NestJS, Validators are typically defined as functions or classes that implement the Validator interface, and can be used to enforce a wide range of validation rules. For example, Validators can be used to ensure that data is of a certain type, that it falls within a specific range of values.

NestJS provides a built-in class-validator library that makes it easy to add validation to your DTOs. This library provides a set of decorators that can be used to define validation rules for the properties of a DTO. For example, you can use the @IsString() decorator to ensure that a property is a string, or the @IsEmail() decorator to ensure that a property is a valid email address.

Using the NestJS built-in class validator for your DTO is not mandatory. You have the flexibility to create your own custom validator and integrate it into your DTO.

NestJS provides a range of tools and libraries for creating custom validators, such as the ValidationPipe and class-transformer packages. These tools allow you to create custom validation rules and apply them to your DTO objects.

By creating your own validator, you have more control over the validation process and can tailor it to meet your specific requirements. This can be particularly useful if you have complex validation rules or need to integrate with external validation services.

Overall, while using the NestJS built-in class validator is convenient and straightforward, creating your own custom validator can offer greater flexibility and control over the validation process.

Below you will find our newly created validator function


import { Schema, Types, ObjectId } from 'mongoose';

export const validateObjectId = (property?: string) => {
  return function (object, propertyName: string) {
    registerDecorator({
      name: 'isRequired',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: {
        message: `Please provide a valid ${property} id`,
      },
      validator: {
        validate(value: any) {
          const isValid = Types.ObjectId.isValid(value);
          if (!isValid) return isValid;
          const validCompany = new Types.ObjectId(value).toString() === value;
          return isValid && validCompany;
        },
      },
    });
  };
};

The code above defines a custom validator function validateObjectId that takes an optional property parameter. This function returns a closure that defines a custom decorator to validate the specified property of an object using the Mongoose ObjectId type. In a simple word, this custom validator can be used to validate ObjectId properties in NestJS DTOs or other objects.

RegisterDecorator:

The registerDecorator function is a NestJS function that registers the custom decorator. It takes an object that contains various options for the decorator, including the decorator name, the target class constructor, the property name, and the validator function.

Validator function:

The validator function checks whether the value of the specified property is a valid MongoDB ObjectId. If the value is not valid, it returns false, and the validation fails. If the value is valid, it checks whether the ObjectId instance can be converted back to the original value without losing any information. If this is not the case, the validation also fails.

Decorator's options:

The decorator's options include a message that is used to generate the validation error message when the validation fails.

Below is a d.t.o that uses our newly created custom validator

we can the add this to our existing d.t.o.

import { validateObjectId } from './path/to/validateObjectId';

export class CreateCompanyDto {


  // Other company fields...

  // Add the validator to the company ID field
  @validateObjectId('company')
  companyId: string;
}

we can use the @ to apply decorators to validate the specific field.

Thanks for reading and i hope it helps.