24-Nodejs Course 2023: Validation Part II: Rules List
Hasan Zohdy
Posted on November 2, 2022
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;
}
}
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());
}
}
}
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;
}
}
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;
}
}
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;
}
}
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());
}
}
}
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;
}
}
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);
}
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;
}
}
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;
}
}
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;
}
}
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
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Posted on November 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.