27-Nodejs Course 2023: Validation Part V: Configurations

hassanzohdy

Hasan Zohdy

Posted on November 2, 2022

27-Nodejs Course 2023: Validation Part V: Configurations

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.

// src/config/validation.ts

const validationConfigurations = {
  rules: {},
};

export default validationConfigurations;
Enter fullscreen mode Exit fullscreen mode

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

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

config.set({
  database: databaseConfigurations,
  validation: validationConfigurations,
});
Enter fullscreen mode Exit fullscreen mode

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.

// 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,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

export default validationConfigurations;
Enter fullscreen mode Exit fullscreen mode

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.

// 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";
Enter fullscreen mode Exit fullscreen mode

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.

// src/core/http/request.ts
import { Validator } from "core/validator";
// ...
Enter fullscreen mode Exit fullscreen mode

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.

// src/config/validation.ts

const validationConfigurations = {
  rules: {
    [RequiredRule.ruleName]: RequiredRule,
    [StringRule.ruleName]: StringRule,
  },
  stopOnFirstFailure: true,
};
Enter fullscreen mode Exit fullscreen mode

Now let's implement it.

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


  /**
   * 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;
        }
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

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.

// src/config/validation.ts

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

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.

// 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,
    };
  }
Enter fullscreen mode Exit fullscreen mode

Error Keys

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

// 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
  },
};
Enter fullscreen mode Exit fullscreen mode

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.

// 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,
      },
    };
  }
Enter fullscreen mode Exit fullscreen mode

Awesome, right?

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

// 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);
  }
Enter fullscreen mode Exit fullscreen mode

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.

// 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,
        },
      };
    }
Enter fullscreen mode Exit fullscreen mode
// 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);
  }
Enter fullscreen mode Exit fullscreen mode

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

// 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,
};
Enter fullscreen mode Exit fullscreen mode

Now let's implement it.

// 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);
  }
Enter fullscreen mode Exit fullscreen mode

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

😍 Join our community

Join our community on Discord 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, 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

Packages & Libraries

React Js Packages

Courses (Articles)

💖 💪 🙅 🚩
hassanzohdy
Hasan Zohdy

Posted on November 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related