DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

24-Nodejs Course 2023: Validation Part II: Rules List

If you're still reading this series, you're awesome! I'm glad you're enjoying it. If you're just joining us, you can find us here in discord. I'm always happy to answer questions and help you out.

We have started our validation in our previous article, now let's get a head directly into the Rule class

Rule Class

The Rule class will receive:

  • It receives the input name.
  • It receives the input value.
  • It receives the input rules.

Now the rule will start validating the input value based on the rules.

Another thing that i just remembered, rule can also need another value from the request body, so we need to pass the request body to the rule class.

// src/core/validator/rule.ts

import { Request } from "core/http/request";

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

  /**
   * Request instance
   */
  protected request!: Request;

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

  /**
   * Set request
   */
  public setRequest(request: Request) {
    this.request = request;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's update the validator class to inject the request to the rule class.

// src/core/validator/index.ts
  /**
   * Scan the validation rules
   */
  public async scan() {
    // get inputs values
    const inputsValues = this.request.only(Object.keys(this.rules));

    for (const input in this.rules) {
      const inputValue = inputsValues[input];
      const inputRules = this.rules[input];

      const rule = new Rule(input, inputValue, inputRules);

      rule.setRequest(this.request);

      await rule.validate();

      if (rule.fails()) {
        this.errorsList.push(rule.errors());
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Now let's start working on the rule class.

Rules List

Oops, i forgot something, that Rule class is supposed to validate a list of rules, it is not supposed to be the rule itself, so let's rename it to RulesList.

Now we'll start create a rules directory inside the validator, each rule will be a class inside this directory.

So now our Validator class will look like this:

// src/core/validator/index.ts
import { Request } from "core/http/request";
import RulesList from "./rules-list";

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

  /**
   * Constructor
   */
  public constructor(
    protected readonly request: Request,
    protected readonly rules: any,
  ) {
    //
  }

  /**
   * Scan the validation rules
   */
  public async scan() {
    // get inputs values
    const inputsValues = this.request.only(Object.keys(this.rules));

    for (const input in this.rules) {
      const inputValue = inputsValues[input];
      const inputRules = this.rules[input];

      const rule = new RulesList(input, inputValue, inputRules);

      rule.setRequest(this.request);

      await rule.validate();

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

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

And our rules list will look like this:

// src/core/validator/rules-list.ts
import { Request } from "core/http/request";

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

  /**
   * Request instance
   */
  protected request!: Request;

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

  /**
   * Set request
   */
  public setRequest(request: Request) {
    this.request = request;

    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Rules List Workflow

Now what we'll do is to create a validate method in our rules list.

This method will loop through the rules and call the rule class for each rule.

After that the rule class will receive the input name and value.

Then the rule will have the same exact validation method as the rules list and validator:

  • validate
  • fails
  • passes
  • errors

Now let's implement these first in the rules list.

// src/core/validator/rules-list.ts
import { Request } from "core/http/request";

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

  /**
   * Request instance
   */
  protected request!: Request;

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

  /**
   * Set request
   */
  public setRequest(request: Request) {
    this.request = request;

    return this;
  }

  /**
   * Start Validating
   */
  public async validate() {
    //
  }

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

Start Rules List validation

Now let's implement our validation as we said before.

// src/core/validator/rules-list.ts

  /**
   * Start Validating
   */
  public async validate() {
    //
    for (const ruleName of this.rules) {
      // get the rule class handler
      const Rule = (RulesList.rulesList as any)[ruleName];

      // create a new object from that rule class
      // pass to it the input name and the input value
      const rule = new Rule(this.input, this.value);

      // inject the request class
      rule.setRequest(this.request);

      // validate

      await rule.validate();

      // if fails
      // push the rule to errors list
      if (rule.fails()) {
        this.errorsList.push(rule.errors());
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

We added so many things here, let's explain them.

First we loop through the rules list, then we get the rule class handler from the rulesTypes which is a static property that contains an object of rules, the key will be the rule name i.e required and its value will be the Rule class handler i.e RequiredRule, then we create a new object from that rule class and pass to it the input name and the input value.

Then we injected the request class to the rule class, then we validate the rule, and if it fails we push the rule to the errors list.

Now let's implement the rulesTypes static property.

// src/core/validator/rules-list.ts
import { Request } from "core/http/request";

export default class RulesList {
  /**
   * Rules List
   */
  public static rulesList = {
    required: RequiredRule,
  };

  /**
   * Errors list
   */
  protected errorsList: any = [];

  /**
   * Request instance
   */
  protected request!: Request;

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

  /**
   * Set request
   */
  public setRequest(request: Request) {
    this.request = request;

    return this;
  }

  /**
   * Start Validating
   */
  public async validate() {
    //
    for (const ruleName of this.rules) {
      // get the rule class handler
      const Rule = (RulesList.rulesList as any)[ruleName];

      // create a new object from that rule class
      // pass to it the input name and the input value
      const rule = new Rule(this.input, this.value);

      // inject the request class
      rule.setRequest(this.request);

      // validate

      await rule.validate();

      // if fails
      // push the rule to errors list
      if (rule.fails()) {
        this.errorsList.push(rule.errors());
      }
    }
  }

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

Too Many Request Injections

If we see, the request class is being injected in almost everywhere, the validator class, the rules list class, and the rule classes.

But we can actually access it directly as it is already a singleton request, so if we want it we can simply import it directly.

Now let's clean up our mess:

Request Class:

// src/core/http/request.ts

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

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

      if (validator.fails()) {
        return this.response.status(422).send({
          errors: validator.errors(),
        });
      }
    }

    return await this.handler(this, this.response);
  }
Enter fullscreen mode Exit fullscreen mode

Validator Class:

// src/core/validator/index.ts
import request from "core/http/request";
import RulesList from "./rules-list";

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

  /**
   * Constructor
   */
  public constructor(protected readonly rules: any) {
    //
  }

  /**
   * Scan the validation rules
   */
  public async scan() {
    // get inputs values
    const inputsValues = request.only(Object.keys(this.rules));

    for (const input in this.rules) {
      const inputValue = inputsValues[input];
      const inputRules = this.rules[input];

      const rule = new RulesList(input, inputValue, inputRules);

      await rule.validate();

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

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

Rules List Class:

// src/core/validator/rules-list.ts
export default class RulesList {
  /**
   * Rules List
   */
  public static rulesList = {
    required: RequiredRule,
  };

  /**
   * Errors list
   */
  protected errorsList: any = [];

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

  /**
   * Start Validating
   */
  public async validate() {
    //
    for (const ruleName of this.rules) {
      // get the rule class handler
      const Rule = (RulesList.rulesList as any)[ruleName];

      // create a new object from that rule class
      // pass to it the input name and the input value
      const rule = new Rule(this.input, this.value);

      // validate

      await rule.validate();

      // if fails
      // push the rule to errors list
      if (rule.fails()) {
        this.errorsList.push(rule.errors());
      }
    }
  }

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

So far so gooood.

Now let's create our first rule, the required rule.

Required Rule

The required rule is a rule that checks if the input is required or not.

Create rules directory (I told you earlier to create it, but sounds you forgot :P) now let's create required.ts file.

// src/core/validator/rules/required.ts
export default class RequiredRule {
  /**
   * Errors list
   */
  protected errorsList: any = [];

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

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

We'll stop at this point here so in our next article we'll start implementing the required rule and couple of other rules.

I hope you enjoy this series, and if you have any questions or suggestions, please let me know in the comments.

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

Top comments (0)