27-Nodejs Course 2023: Validation Part V: Configurations
Tags: node, typescript, mongodb, fastify
Published: 2022-11-02 12:51:24

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

We did so far an amazing job about validation, let's move it to the next level by adding some configurations to our validation process.

## Configurations

Open `src/config` and create `validation.ts` file.

```ts
// src/config/validation.ts

const validationConfigurations = {
  rules: {},
};

export default validationConfigurations;
```

Just an empty object for now, now let's update the index file of the configuration to inject it in the general configurations.

```ts
// src/config/index.ts
import config from "@mongez/config";
import { databaseConfigurations } from "config/database";
import validationConfigurations from "./validation";

config.set({
  database: databaseConfigurations,
  validation: validationConfigurations,
});
```

Now can access the configurations from anywhere in the application.

## Rules List

Remember the `rulesTypes` that we added in `RulesList` class? we'll lets remove it and use the configurations instead.

```ts
// src/validation/rules-list.ts
import config from "@mongez/config";

export default class RulesList {
  /**
   * Errors list
   */
  protected errorsList: any = [];

  /**
   * Constructor
   */
  public constructor(
    protected readonly input: string,
    protected readonly value: any,
    protected readonly rules: any,
  ) {
    //
  }

  /**
   * Validate the rules
   */
  public async validate() {
    for (const ruleName of this.rules) {
      // the rule will be in the rules section under the validation object
      const RuleClass = config.get(`validation.rules.${ruleName}`);

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

      await rule.validate();

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

  /**
   * Check if validator fails
   */
  public fails() {
    return this.errorsList.length > 0;
  }

  /**
   * Check if validator passes
   */
  public passes() {
    return this.errorsList.length === 0;
  }

  /**
   * Get errors list
   */
  public errors() {
    return {
      input: this.input,
      errors: this.errorsList,
    };
  }
}
```

What we updated here we removed `rulesTypes` and we replaced it with `config.get('validation.rules.${ruleName}')` which will get the rule from the configurations.

Now let's update our rules in the configurations file

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

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
};

export default validationConfigurations;
```

We imported the `StringRule` and `RequiredRule` which we didn't export it yet from `validator` folder, so let's do it.

> Remember the ruleName static property? that's why we created it to use it here with more fancy code :)

We need first to move the validator class to a separate file so we can make the index file more readable.

Rename `index.ts` to `validator.ts` and now create `index.ts` file.

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

We exported the Validator class, Rules List, Rule class, RequiredRule and StringRule classes.

So when we create a new rule, don't forget to export it from the index file.

Now update the `request.ts` file and update the import from `validator` to `core/validator`.

```ts
// src/core/http/request.ts
import { Validator } from "core/validator";
// ...
```

Now give it a try, it should be working as expected.

## Stop on first failure

We can add a new configuration to stop the validation process on the first failure so we don't need to validate all the rules if the first one fails.

```ts
// src/config/validation.ts

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  stopOnFirstFailure: true,
};
```

Now let's implement it.

Go to `rules-list.ts` file and update the `validate` method.

```ts

  /**
   * Validate the rules
   */
  public async validate() {
    for (const ruleName of this.rules) {
      // the rule will be in the rules section under the validation object
      const RuleClass = config.get(`validation.rules.${ruleName}`);

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

      await rule.validate();

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

        // stop the loop after it fails
        if (config.get("validation.stopOnFirstFailure")) {
          break;
        }
      }
    }
  }
```

Check the code inside the `if` statement, we check if the `stopOnFirstFailure` is true, if it is true we break the loop.

## Return Error Strategy

We can add another configuration to return the error strategy, we can return the first error or all errors.

```ts
// src/config/validation.ts

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  stopOnFirstFailure: true,
  returnErrorStrategy: "first", // first or all
};
```

Now we added a new configuration called `returnErrorStrategy` and we set it to `first`.

So we can choose between `first` and `all` to determine how we can return the error.

Let implement it.

```ts
// src/validation/rules-list.ts

  /**
   * Get errors list
   */
  public errors() {
    if (config.get("validation.returnErrorStrategy") === "first") {
      return {
        input: this.input,
        errors: this.errorsList[0],
      };
    }

    return {
      input: this.input,
      errors: this.errorsList,
    };
  }
```

## Error Keys

We can also control the `error keys` that will be return to response.

```ts
// src/config/validation.ts

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  stopOnFirstFailure: true,
  returnErrorStrategy: "first", // first or all
  keys: {
    response: 'errors', // the key that will be used to return the entire errors
    inputKey: "input",
    inputErrors: "errors", // for all errors
    inputError: "error", // for first error
  },
};
```

We added a new configuration called `keys` and we set the `response` key to `errors` which will be used to return the entire errors.

The `inputKey` will be used to return the input key.

The `inputErrors` will be used to return the errors list if we choose `all` strategy.

The `inputError` will be used to return the first error if we choose `first` strategy.

Now let's implement it.

```ts
// src/core/validator/rules-list.ts
  /**
   * Get errors list
   */
  public errors() {
    const responseKey = config.get("validation.keys.response");
    const inputKey = config.get("validation.keys.inputKey");
    const inputErrorsKey = config.get("validation.keys.inputErrors");
    const inputErrorKey = config.get("validation.keys.inputError");

    if (config.get("validation.returnErrorStrategy") === "first") {
      return {
        [responseKey]: {
          [inputKey]: this.input,
          [inputErrorKey]: this.errorsList[0],
        },
      };
    }

    return {
      [responseKey]: {
        [inputKey]: this.input,
        [inputErrorsKey]: this.errorsList,
      },
    };
  }
```

Awesome, right?

Now let's update `request.ts` file and update the `validate` method.

```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);

      await validator.scan(); // start scanning the rules

      if (validator.fails()) {
        const responseKey = config.get("validation.keys.response");
        return this.response.status(422).send({
          [responseKey]: validator.errors(),
        });
      }
    }

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

## Configurations defaults

This will work perfectly, but if we did not forget to import the configurations, what if we forgot to add the configurations?

In that case, we **must** add the default configurations, `THANKFULLY` `config.get` accepts second argument as default value, so let's update all the configurations to use the default value.

```ts
// src/core/validator/rules-list.ts
  
    /**
    * Get errors list
    */
    public errors() {
      const responseKey = config.get("validation.keys.response", "errors");
      const inputKey = config.get("validation.keys.inputKey", "input");
      const inputErrorsKey = config.get("validation.keys.inputErrors", "errors");
      const inputErrorKey = config.get("validation.keys.inputError", "error");
  
      if (config.get("validation.returnErrorStrategy", "first") === "first") {
        return {
          [responseKey]: {
            [inputKey]: this.input,
            [inputErrorKey]: this.errorsList[0],
          },
        };
      }
  
      return {
        [responseKey]: {
          [inputKey]: this.input,
          [inputErrorsKey]: this.errorsList,
        },
      };
    }
```

```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);

      await validator.scan(); // start scanning the rules

      if (validator.fails()) {
        const responseKey = config.get("validation.keys.response", "errors");
        return this.response.status(422).send({
          [responseKey]: validator.errors(),
        });
      }
    }

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

## Response Status Error Configuration

> That's my bad, we'll use status 400, i wrote it in the previous articles as 422, which is another status code.

We can also add a configuration to control the response status code when the validation fails which we set to to [400 which is a bad request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400)

```ts
// src/config/validation.ts

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  stopOnFirstFailure: true,
  returnErrorStrategy: "first", // first or all
  keys: {
    response: "errors", // the key that will be used to return the entire errors
    inputKey: "input",
    inputErrors: "errors", // for all errors
    inputError: "error", // for first error
  },
  responseStatus: 400,
};
```

Now let's implement it.

```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);

      await validator.scan(); // start scanning the rules

      if (validator.fails()) {
        const responseKey = config.get("validation.keys.response", "errors");
        return this.response
          .status(config.get("validation.responseStatus", 400))
          .send({
            [responseKey]: validator.errors(),
          });
      }
    }

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

And that's it!

We have now a powerful validation class with a lot of configurations.

## 🎨 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)