Validate Request Input in Nestjs

Validate Request Input in Nestjs

Post Date : 2022-07-29T15:17:27+07:00

Modified Date : 2022-07-29T15:17:27+07:00

Category: frameworks nestjs

Tags: nestjs

Note: All of demo source code you can find our in github nestjs boilerplate

It is best practice to validate the correctness of any data sent into a web application

There are several libraries support us to verify data :

And in typescript, we have a special library that support decorator

  • Class Validator - internally uses validator.js to perform validation. Class-validator works on both browser and node.js platforms

Let’s dive in class validator usage example. Let’s say that we have a requirement for example entity’s data like this

{
    "title": {
        "dataType": string,
        "required": true,
        "maxLength": 75,
        "minLength": 1,
    },
    "description": {
        "dataType": string,
        "required": false,
        "maxLength": 160,
    },
    "content": {
        "dataType": string,
        "required": false
    },
    "status": {
        "dataType": enum,
        "required": false,
        "default": 1,
        "enum": {pending:1, published:2, archived:3}, // can not be archived when creating
    },
    "tags": {
        "dataType": Array[string],
        "required": false,
        "arrayMaxLength": 5,
        "eachValidation": {
            "minLength": 3,
            "maxLength": 5,
        }
    }
}

Class validator supports 2 ways for validation

Create your class and put some validation decorators on the properties you want to validate

In real world projects, the request body will be JSON object, so that you need to create your class object by assign values from JSON object.

npm install class-validator --save
const postData = {
  title: "hello",
  description: "the greatest post",
  content: "lorem ipsum ...",
};
let post = new Post();
post.title = postData.title;
post.description = postData.description;
post.content = postData.content;

OR

let post = new Post(postData);

And in the constructor of this class, you should binding the properties that corresponded with object’s values

Randomly, I’ve found a library for typescript that support us easily transform your JSON object into an instance of a class. It was called “Class Transformer”

npm install class-transformer reflect-metadata --save

Should import this into your main.ts file

import "reflect-metadata";

Example of using class-transformer and class-validator

import { plainToInstance } from "class-transformer";
import {
  IsDate,
  IsDateString,
  IsNotEmpty,
  IsNumber,
  registerSchema,
  validate,
  ValidationSchema,
  ValidatorOptions,
} from "class-validator";

class LessionExample {
  @IsNotEmpty()
  title: string;
  @IsNumber()
  @IsNotEmpty()
  status: number;
  @IsDateString()
  @IsNotEmpty()
  publishedDate: Date;
}

const validatorOptions: ValidatorOptions = {
  skipMissingProperties: false,
};

describe("test class validator and class transformer utils", () => {
  describe("test class validator and class transformer utils - with decorator", () => {
    let lession: LessionExample = null;
    beforeEach(() => {
      const inputJson = {
        title: "Class validator",
        status: 1,
        publishedDate: "2022-07-29 00:00:00",
      };
      lession = plainToInstance(LessionExample, inputJson);
    });
    it("should transform an object to class", () => {
      console.log("typeof lession.publishedDate", typeof lession.publishedDate);
      expect(lession).toBeInstanceOf(LessionExample);
    });

    it("should be able to validate the instance data", async () => {
      const errors = await validate(lession, validatorOptions);
      console.log(errors);
      expect(errors.length).toEqual(0);
    });
  });
});

When using class validator, if your data is valid, it will return an empty array of error

validate(post).then((errors) => {
  // errors is an array of validation errors
  if (errors.length > 0) {
    console.log("validation failed. errors: ", errors);
  } else {
    console.log("validation succeed");
  }
});

Defining validation schema without decorators to validate your json object

import {
  registerSchema,
  validate,
  ValidationSchema,
  ValidatorOptions,
} from "class-validator";

const validatorOptions: ValidatorOptions = {
  skipMissingProperties: false,
};

describe("test class validator and class transformer utils", () => {
  interface LessionInputDTO {
    title: string;
    status: number;
    publishedDate: string;
  }

  describe("test class validator and class transformer utils - using schema", () => {
    let lessionInput: Partial<LessionInputDTO> = null;
    let lessionSchema: ValidationSchema = null;
    beforeAll(() => {
      lessionInput = {
        //title: 'Class validator',
        status: 1,
        publishedDate: "2022-07-29 00:00:00",
      };
      lessionSchema = {
        name: "createLessionSchema",
        properties: {
          title: [{ type: "isNotEmpty" }],
          status: [
            { type: "isNotEmpty", constraints: [] },
            { type: "isNumber" },
          ],
          publishedDate: [
            { type: "isNotEmpty", constraints: [] },
            { type: "isDateString" },
          ],
        },
      };
      // registered schema
      registerSchema(lessionSchema);
    });

    it("should be able to validate the instance data", async () => {
      lessionInput.title = "";
      delete lessionInput.title;
      const errors = await validate(
        lessionSchema.name,
        {
          ...lessionInput,
          status: "",
        },
        validatorOptions
      );
      console.log(lessionInput, errors);
      expect(errors.length).toEqual(0);
    });
  });
});

It is very cool right? BUT …

RedAlert

image

From this PR 568, CLASS VALIDATOR WAS NOT SUPPORTED “Validate with schema anymore”

image

Let’s dive in more details with NestJS standard approach

NestJS provide us a built-in pipe which includes class-validator and class transformer.

Using NestJS Pipes to Validate your Data