28-Nodejs Course 2023: Validation Part VI: Custom Validation
Tags: node, typescript, mongodb, fastify
Published: 2022-11-02 16:18:07

---
title: 28-Nodejs Course 2023: Validation Part VI: Custom Validation
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
#cover_image: 
# Use a ratio of 100:42 for best results.
---

We have made such an amazing progress in validation, but there is still more to be done.

## Configurations Types

We have created configurations list successfully, but let's give it a more fancy touch, let's define a type for it.

Create `types.ts` file in `src/core/validator` directory.

```ts
// src/core/validator/types.ts

export type ValidationConfigurations = {
  /**
   * Whether to stop validator after first failed rule
   *
   * @default true
   */
  stopOnFirstFailure?: boolean;
}
```

We added only one property for now to see how i'm writing it.

We first write documentation for it, if we set a default value then we write it in the documentation, and then we define the type.

Now let's add the rest of the configurations.

```ts
// src/core/validator/types.ts

import Rule from "./rules/rule";

export type ValidationConfigurations = {
  /**
   * Whether to stop validator after first failed rule
   *
   * @default true
   */
  stopOnFirstFailure?: boolean;
  /**
   * Return Error Strategy
   * If strategy is `first` then it will return a single error in string from the rule errors list
   * If strategy is `all` then it will return an array of string that contains all errors.
   *
   * The `all` strategy will be affected as well with `stopOnFirstFailure` if it is set to `true`
   * and strategy is set to `all` it will always return an array with one value
   *
   * @default first
   */
  returnErrorStrategy?: "first" | "all";
  /**
   * Response status code
   *
   * @default 400
   */
  responseStatus?: number;
  /**
   * Validation keys
   */
  keys?: {
    /**
     * Response error key that will wrap the entire errors
     *
     * @default errors
     */
    response?: string;
    /**
     * Input key name
     *
     * @default input
     */
    inputKey?: string;
    /**
     * Single Error key (when strategy is set to first)
     *
     * @default error
     */
    inputError?: string;
    /**
     * Multiple Errors key (when strategy is set to all)
     *
     * @default errors
     */
    inputErrors?: string;
  };
  /**
   * Rules list that will be used in the validation process
   */
  rules?: Record<string, typeof Rule>;
};
```

All types are pretty much self explained, except the `rules` one, we used the `Record` type which means we're telling Typescript the `rules` will be an object, its key will be `string` and its value will be typeof `Rule` class.

Now let's export it in the validator index then import it in the `validator.ts` configuration file.

```ts
// src/core/validator/index.ts
export { default as RequiredRule } from "./rules/required";
export { default as Rule } from "./rules/rule";
export { default as StringRule } from "./rules/string";
export * from "./types";
export { default as Validator } from "./validator";
```

```ts
// src/config/validation.ts
import {
  RequiredRule,
  StringRule,
  ValidationConfigurations,
} from "core/validator";

const validationConfigurations: ValidationConfigurations = {
  stopOnFirstFailure: true,
  returnErrorStrategy: "first",
  responseStatus: 400,
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  keys: {
    response: "messages",
    inputKey: "key",
    inputError: "error",
    inputErrors: "errors",
  },
};

export default validationConfigurations;
```

The amazing thing about typescript, is that now you can use the auto complete feature in your vscode.

Try to remove all configurations and inside the object press `ctrl + space` and you will see all the configurations that you can use.

## Missing Rule Handler

As we're going big, we need to make sure that our code is working as expected, what if we tried to add a rule that does not exist in the `rules` list?

In that case the application will definitely crash, so let's add a handler for that.

```ts
// src/core/validator/rules-list.ts
import chalk from "chalk";

  /**
   * Validate the rules
   */
  public async validate() {
    for (const ruleName of this.rules) {
      const RuleClass = config.get(`validation.rules.${ruleName}`);

      if (!RuleClass) {
        throw new Error(
          chalk.bold(
            `Missing Rule: ${chalk.redBright(
              ruleName,
            )} rule is not listed in ${chalk.cyan(
              "validation.rules",
            )} configurations,`,
          ),
        );
      }

      const rule = new RuleClass(this.input, this.value);

      await rule.validate();

      if (rule.fails()) {
        this.errorsList.push(rule.error());

        if (config.get("validation.stopOnFirstFailure", true)) {
          break;
        }
      }
    }
  }
```

We added a check here to see if the rule exists in the `rules` list, if not then we throw an error.

But we need to catch that error to be displayed as you won't see any error in the console.

Open the request class to wrap the validator in a try catch block.

```ts
// src/core/http/request.ts

  /**
   * Execute the request
   */
  public async execute() {
    if (this.handler.validation) {
      const validator = new Validator(this, this.handler.validation.rules);

      try {
        await validator.scan(); // start scanning the rules
      } catch (error) {
        // this is needed to catch the error thrown by the missing rule handler
        console.log(error);
      }

      if (validator.fails()) {
        const responseErrorsKey = config.get(
          "validation.keys.response",
          "errors",
        );
        const responseStatus = config.get("validation.responseStatus", 400);

        return this.response.status(responseStatus).send({
          [responseErrorsKey]: validator.errors(),
        });
      }
    }

    return await this.handler(this, this.response);
  }
```

Now go to `create-user` and add `email` rule that does not exist in the `validation.rules` list.

> Mark `stopOnFirstFailure` as `false` in the `validation.ts` file to see the difference.

You should now see something like this in your terminal:

![Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/udwgrp9fryn50nsxyovj.png)

## Custom Validation

So we're so far now good with validation, but sometimes rules are not enough, we might need to do some custom validations on the requests besides the rules.

That's where we'll introduce a new `validate` method in the handler `validation` object.

## Validate Method

The handler (controller) has a `validation` property, which contains the `rules` property to validate inputs.

We will also add the ability to add a `validate` method to the `validation` object so we can do any custom validation.

## How it works

The `validate` method will receive the request and the response as parameters, and it will be executed after the rules validation.

If the `validate` method returns a value, this will be returned, otherwise the handler will be executed.

Also we'll make it `async` function so we can perform any async operation inside it.

Let's jump into the code.

```ts
// src/core/http/request.ts

  /**
   * Execute the request
   */
  public async execute() {
    if (this.handler.validation) {
      // rules validation
      if (this.handler.validation.rules) {
        const validator = new Validator(this, this.handler.validation.rules);

        try {
          await validator.scan(); // start scanning the rules
        } catch (error) {
          console.log(error);
        }

        if (validator.fails()) {
          const responseErrorsKey = config.get(
            "validation.keys.response",
            "errors",
          );

          const responseStatus = config.get("validation.responseStatus", 400);

          return this.response.status(responseStatus).send({
            [responseErrorsKey]: validator.errors(),
          });
        }
      }

      // custom validation
      if (this.handler.validation.validate) {
        const result = await this.handler.validation.validate(
          this,
          this.response,
        );

        if (result) {
          return result;
        }
      }
    }

    return await this.handler(this, this.response);
  }
```

We wrapped the `rules` validation with a new if statement, to see if the request has `rules` property.

If the rules property exists, then execute and validate it first, if failed, then stop executing the handler and the custom validator as well.

Now let's give it a try, open `create-user` handler and add the `validate` method.

```ts
// src/app/users/controllers/create-user.ts
import { Request } from "core/http/request";

export default async function createUser(request: Request) {
  const { name, email } = request.body;

  return {
    name,
  };
}

createUser.validation = {
  // rules: {
  //   name: ["required", "string"],
  //   email: ["required", "string"],
  // },
  validate: async () => {
    return {
      error: "Bad Request",
    };
  },
};
```

We commented the rules only for testing purposes, and added a `validate` method that returns an object.

If the validate returns any value, then it will return that value, otherwise it will execute the handler.

## Conclusion

We've reached the end of this article, we've added the configurations types, we also wrapped the rules validation in a try catch block to catch any errors thrown by the rules.

Also we added a custom validation method to the handler, so we can do any custom validation on the request.

## 🎨 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) to get help and support (Node Js 2023 Channel).

## 🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on [Youtube](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), the course is in Arabic language.

## 💰 Bonus Content 💰

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

- [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
- [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
- [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

- [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
- [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
- [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

- [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

- [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)