CategoryTagArticle

admin

I'm a Full-stack developer

Tag

Linked List
Data Structure
Chat GPT
Design Pattern
Microservices
API
AWS CDK
ReactJS
AWS Lightsail
Flutter Mobile
JOI - API schema validation
Published date: 12/06/2023

In this article, I introduce about Joi validation module to check data.


Data validation is one of topics that I am interesting. I always review my code after developed features or fixed bugs. There are many places where need to validate data, it is really terrible. Some cases, we need to validate data input because ensure the data into API, it will not make any problems to crash system.


As in the example below, the logic to check the data is really complex.


if (!req.body.email) {
  throw new Exception('Email is missing.') 
}

if (!req.body.password) {
  throw new Exception('Password is missing.') 
}

if (req.body.password.length < 8) {
  throw new Exception('Password must be at least 8 characters.') 
}


In this article, I introduce about Joi validation module to check data.


What is JOI?


Joi is a library that can help you check if the JSON structure is correct with the structure you want.


Why use Joi to check data before processing?


Verify quickly.

Limit exceptions.

Limit unnecessary processing when the user inputs the wrong value.

Ensure data integrity, and limit data rollback in the database.


JOI validation into REST API using Express.

Create project
mkdir joi-schema-validation


Go to the folder you just created above
cd joi-schema-validation


Install dependencies
npm install body-parser express joi


Create a new file app.ts in the root directory to set up the Express app
import express, { Express, NextFunction } from 'express';

import { json, urlencoded } from 'body-parser';

const app: Express = express();

app.use(urlencoded({ limit: '10mb', extended: true, parameterLimit: 50000 }));

app.use('/api', routes);

app.listen(9000, () => {
  console.info('Listening to port 9000');
});


Create a new file routes.ts
const router = express.Router();
const defaultRoute: IRoute[] = [
  {
    path: '/auth',
    route: new AuthRoute().getRouter(),
  },
];

defaultRoute.forEach((route) => {
  router.use(route.path, route.route);
});

export default router;


Create a directory containing schemas
mkdir schema-validation


Create the file base.schema-validation.ts to contain the base validations
import Joi from 'joi';

export class BaseSchemaValidation {
  protected readonly _httpRequest: { params?: any; query?: any; body?: any };

  constructor() {
    this._httpRequest = {};
  }
  
  withBody(body) {
    this._httpRequest.body = body;

    return this;
  }
  
  build() {
    return {
      ...(this._httpRequest.body && { body: this._httpRequest.body }),
      ...(this._httpRequest.query && { query: this._httpRequest.query }),
      ...(this._httpRequest.params && { query: this._httpRequest.params }),
    };
  }
}


Create the file login.schema-validation.ts to check the input for the login route
import Joi from 'joi';

export class LoginSchemaValidation extends BaseSchemaValidation {
  constructor() {
    super();
  }
  
  login() {
    return super
      .withBody({
        email: Joi.string().email().required(),
        password: Joi.string().custom(password).required(),
      })
      .build();
  }
}

const password = (value: string, helpers: CustomHelpers) => {
  if (value.length < 8) {
    return helpers.message({ custom: 'password must be at least 8 characters' });
  }
  if (!value.match(/\d/) ?? !value.match(/[a-zA-Z]/)) {
    return helpers.message({ custom: 'password must contain at least 1 letter and 1 number' });
  }
  return value;
};


Create a new folder middlewares in the root directory
mkdir middlewares


Then create a new file schema-validator.middleware.ts
import { NextFunction, Request, Response } from 'express';

import Joi, { ValidationOptions } from 'joi';

const pick = (object: Record<string, any>, keys: string[]) =>
  keys.reduce((obj: any, key: string) => {
    if (object && Object.prototype.hasOwnProperty.call(object, key)) {
      obj[key] = object[key];
    }
    return obj;
  }, {});

const schemaValidatorMiddleware = (schema: any, body?: any, options?: ValidationOptions) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const validSchema = pick(schema, ['params', 'query', 'body']);
      const object = pick(req, Object.keys(validSchema));
      const { value, error } = Joi.compile(validSchema)
        .prefs({ errors: { label: 'key' } })
        .validate(object, { abortEarly: false });

      if (error) {
        console.log(
          `Validate request has an error ${JSON.stringify({
            ...error,
          })}`,
        );

        const errorMessage = {};

        error.details.forEach((e: any) => {
          errorMessage[e.path[0]] = [...(errorMessage[e.path[0]] ?? []), e.message];
        });

        return res.status(422).json(errorMessage);
      }

      Object.assign(req, value);

      return next();
    } catch (error: any) {
      console.log('Validate request has an error', {
        ...error,
      });

      return res.status(422).json({
        message: 'Validate request has an error'
      })
    }
  };
};

export { schemaValidatorMiddleware };


Add the schema validator middleware to the auth route auth.route.ts
export class AuthRoute {
  initialRoutes() {
    this.loginWithPassword([schemaValidatorMiddleware(new LoginSchemaValidation().login())]);
  }
  
  login(middleware: any = []) {
    this.router.post('/login', middleware, (req: Request, res: Response, next: NextFunction) =>
      this.controller.login(req, res, next),
    );
  }
}


Testing


Let's run your application
npm start


No body request



Email and password are not in the correct format


Summary


In this article, you created a schema to check input data before executing logic for a REST API using Joi and validating data from an HTTP request using middleware.

Having consistent data ensures that it will behave in a reliable and expected way when you reference it in your application.


Good luck to you, hope this post is of value to you!!!!

Recommend

TypeScript Design Pattern - Singleton
admin07/08/2023

TypeScript Design Pattern - Singleton
The singleton ensures only a single install is created in a class and provides a method to access this instance everywhere in a codebase.
What are the SOLID principles?
admin17/06/2023

What are the SOLID principles?
If we want to have good software, the infrastructure should be good first. We should learn more techniques that help to have better quality.
NodeJS Verify and Decode Cognito JWT Tokens
admin12/06/2023

NodeJS Verify and Decode Cognito JWT Tokens
In this article, I will show you how to verify and decode the Cognito JWT Tokens token.
Newest

Semantic Versioning NodeJS
admin07/07/2023

Semantic Versioning NodeJS
How to Use Semantic Versioning in NPM
TypeScript Design Pattern - Adapter
admin08/08/2023

TypeScript Design Pattern - Adapter
This design pattern acts as a bridge between two different interfaces.
🚀 Using Bitwise Oprators to build a RBAC in Node.js 🚀
admin13/04/2024

🚀 Using Bitwise Oprators to build a RBAC in Node.js 🚀
In this article, I will illustrate to you how to build an RBAC in Node.js using Bitwise Operators.
Đinh Thành Công Blog

My website, where I write blogs on a variety of topics and where I have some experiments with new technologies.

hotlinelinkedinskypezalofacebook
DMCA.com Protection Status
Feedback
Name
Phone number
Email
Content
Download app
hotline

copyright © 2023 - AGAPIFA

Privacy
Term
About