Mar 17, 2023
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 anundefined
value if the data is valid or otherwise aValidationError
. - A
value
property, which will contain the type converted values of the data object, which means thatjoi
will automatically convert these values to match their declared type, like for example: aJoi.number()
will be converted to an integer, aJoi.date()
will be converted to aDate
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.
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 theschemaValidator()
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