Mar 17, 2023

Build a Custom Payload Validation Middleware for ExpressJS

Build a Custom Payload Validation Middleware for ExpressJS

The second and probably most important part of the data handling process after parsing is called validation, as indeed, parsing alone is not enough to guarantee that the data sent by a user or another application respects the API contract.

In this article, you'll learn how to create your own data validation middleware for the Express framework using a package named joi.

Validating objects with Joi

joi is a library used for validating JavaScript objects.

$ npm install --save joi

The validation of an object with joi is a two step process:

  • First, we need to construct the schema of this object using types and constraints.
  • Second, we need to validate this schema against an arbitrary value.

Creating a schema

To create a schema, we can use the top-level object() method exported by the joi module, that takes as argument an object where:

  • The keys are the name of the fields we want to verify.
  • The values are the types and constraints of these fields.
const Joi = require('joi');

const schema = Joi.object({
  fieldName: Joi.type().constraint()
});

Validating data

Once the schema is defined, we can use its validate() method to verify that the data matches the described format, which returns an object containing two properties:

  • An error property, which will either contain an undefined value if the data is valid or otherwise a ValidationError.
  • A value property, which will contain the type converted values of the data object, which means that joi will automatically convert these values to match their declared type, like for example: a Joi.number() will be converted to an integer, a Joi.date() will be converted to a Date object.
const { error, value } = schema.validate(data);

Creating a payload validation middleware

The request-response lifecycle

At a schematic level, the role of a payload validation middleware is to intercept the incoming request, match its payload against a specific schema, and either brake the request-response lifecycle in case of an error, or forward the request to the next component in the call stack.

Request-response lifecycle

Creating a schema

Before creating the middleware, let's start by creating a new directory named schemas, and within it a new file named signin.js that exports a joi object with:

  • An email property, that represents a valid email address.
  • A password property, that represents an alphanumeric string with a length comprised between 8 and 30 characters.
// File: schemas/signin.js

const Joi = require('joi');

const schema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{8,30}$')).required()
});

module.exports = schema;

Once we've done that, let's create another file named index.js whose only responsibility is to export the schemas defined in the schemas directory as a single object for future use.

// File: schemas/index.js

const signin = require('./signin');

module.exports = {
  signin
};

Creating the middleware

In a new file named schema-validator.js, let's import the schemas we've just defined, and export a function that takes as argument the name of the schema we want to use, and returns a middleware function.

// File: schema-validator.js

const schemas = require('./schemas');

module.exports = (schemaName) => (req, res, next) => {
  const schema = schemas[schemaName] || null;
  if (!schema) {
    return res.sendStatus(500);
  }

  const { error } = schema.validate(req.body);
  if (error) {
    return res.sendStatus(400);
  }

  return next();
};

When invoked, this middleware will first try to retrieve the requested schema from the schemas object, or respond to the client right with an HTTP 500 (Internal Server Error) if it doesn’t exist.

It will then attempt to validate the incoming request’s payload using the validate() method of the schema object, and either return an HTTP 400 (Bad Request) if the payload is invalid, or forward the request to the next component in the call stack using the next() function.

Testing the middleware

To test this middleware, we can use the following Node.js application that implements a single POST /signin route that:

  • Parses the incoming request in the JSON format using the express.json() built-in middleware.
  • Validates the parsed payload contained in the req.body property using the schemaValidator() middleware.
  • Forwards the request to the controller that responds with an HTTP 200 (OK).
// File: app.js

const express = require('express');
const app = express();

const schemaValidator = require('./schema-validator');

app.post(
  '/signin',
  express.json(),
  schemaValidator('signin'),
  (req, res) => {
    res.sendStatus(200);
  }
);

app.listen(3000);

We can now run this application in a new terminal window using the following command:

$ node app.js

And send a valid email and password, which should result in the application responding with an HTTP 200 (OK).

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"user@test.com","password":"helloworld"}' \
127.0.0.1:3000/signin

To verify that the middleware is working, we can now send another request with an invalid email address which should result in the application responding with an HTTP 400 (Bad Request).

$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"user","password":"helloworld"}' \
127.0.0.1:3000/signin

Related posts