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.
Joi is a library that can help you check if the JSON structure is correct with the structure you want.
Verify quickly.
Limit exceptions.
Limit unnecessary processing when the user inputs the wrong value.
Ensure data integrity, and limit data rollback in the database.
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), ); } }
Let's run your application
npm start
No body request
Email and password are not in the correct format
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!!!!