Validate HTTP Parameters & Body in Express
19 min read·Jan 1, 2025
The second and probably most important part of the data handling process after parsing is called validation, as parsing alone is not enough to guarantee that the data sent by a user or another application respects the API contract.
In this context, validation refers to the process of ensuring the accuracy of the parsed data by matching its value, type, and format against a predefined model.
Get started with Joi
Joi is a JavaScript library used for validating 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, which in this case will be the parsed message body of an incoming request.
Create a validation schema
In Joi, a schema refers to a set of rules that define the structure, types, and constraints that an object must adhere to in order to be considered valid.
To create a schema, we can use the top-level object() method exported by the joi module that takes as argument an object:
const Joi = require('joi');
const schema = Joi.object({
property: Joi.type().constraint()
});
Where:
propertyis the name of an object property (e.g.email).type()is a chainable function used to specify the data type (e.g.string())constraint()is a chainable function used to specify a constraint (e.g.email()).
Example
In this example, the user schema has an email and a password property:
const Joi = require('joi');
const user = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).max(20).required()
});
Where:
emailis a mandatory string representing a valid email address.passwordis a mandatory string of a length between 8 and 20 characters.
Validate data using a schema
Once the schema is defined, we can use its validate() method to verify that the data matches the described format, which returns two objects:
const { error, value } = schema.validate(data);
Where:
errorwill either contain anundefinedvalue if the data is valid or aValidationErrorotherwise.valuewill contain the type converted values of the data object. This means thatjoiwill automatically convert these values to match their declared type, for example, aJoi.number()will be converted to an integer, aJoi.date()will be converted to aDateobject and so on.datais an arbitrary JavaScript object to validate.
Example
Let's consider this module, that exports a Joi schema:
const Joi = require('joi');
module.exports = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).max(20).required()
});
Let's consider this script, that uses the schema exported by the user-schema.js module to validate objects:
const userSchema = require('./user-schema.js');
const userA = {
email: 'johndoe@mail.com',
password: 'helloworld'
};
console.log(userSchema.validate(userA));
const userB = {
email: 'johndoe',
password: ''
};
console.log(userSchema.validate(userB));
Which will produce this output:
$ node index.js
validation 1 { value: { email: 'johndoe@mail.com', password: 'helloworld' } }
validation 2 {
value: { email: 'johndoe', password: '' },
error: [Error [ValidationError]: "email" must be a valid email] {
_original: { email: 'johndoe', password: '' },
details: [ [Object] ]
}
}
Create a payload validation middleware
In Express, the role of a payload validation middleware is to streamline the validation process by checking the data parsed from the incoming HTTP request, and either:
- Break the request-response cycle by sending an error response back to the client if the data doesn't match (e.g., missing fields, invalid data types).
- Forward the request to the next component in the route's call stack.
This helps avoid unnecessary processing at the controller level and mitigate the risk of potential errors due to incorrect or invalid data.
Create a schema directory
The first step consists in creating a directory that contains all the schema definitions used by the middleware for the entire application, and within this directory, an index file in charge of exporting the schemas as a single object.
Example
Let's create a new directory named schemas:
$ mkdir schemas
Within this directory, let's create a new file named signup.js that exports a schema for validating the payload sent to a sign up endpoint:
const Joi = require('joi');
const signupSchema = Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).max(20).required(),
});
module.exports = signupSchema;
Within this directory, let's also create a new file named index.js that exports an object containing the signup schema:
const signupSchema = require('./signup');
module.exports = {
signup: signupSchema
};
Create a validation middleware
Let's create a new directory named middlewares:
$ mkdir middlewares
Within this directory, let's create a new file named payload-validator.js that exports a higher-order function that takes as argument the name of the schema we want to use and returns a middleware function:
const schemas = require('../schemas');
const payloadValidator = (schemaName) => (req, res, next) => {
//
};
module.exports = payloadValidator;
Let's now verify whether the specified schemaName is a valid key of the schemas object and respond with an HTTP 500 Internal Server Error if mistakes were made on the developer's end, such as an oversight of schema definition or a typo in the schema's name.
const schemas = require('../schemas');
const payloadValidator = (schemaName) => (req, res, next) => {
const schema = schemas[schemaName] || null;
if (schema) {
//
} else {
res.sendStatus(500);
}
};
module.exports = payloadValidator;
Finally, if the specified schema exists, the middleware will call the schema's validate() method with the request's payload contained in the req.body object, and either:
- Break the request-response cycle by sending a HTTP
400 Bad Requestif the payload is invalid. - Forward the request to the next component in the call stack using the
next()function otherwise.
const schemas = require('../schemas');
const payloadValidator = (schemaName) => (req, res, next) => {
const schema = schemas[schemaName] || null;
if (schema) {
const { error } = schema.validate(req.body);
if (error) {
res.sendStatus(400);
} else {
next();
}
} else {
res.sendStatus(500);
}
};
module.exports = payloadValidator;
Use the validation middleware
To use the validation middleware on a specific route, you can invoke it right after the parsing middleware using one of the schema names exported by the schemas/index.js file:
app.METHOD(PATH, PARSER, payloadValidator('schemaName'), HANDLER);
Where:
PARSERis a parsing middleware function, likeexpress.json().HANDLERis a request listener function.
Example
Let's consider this directory structure, that represents the files we've just created:
~/projects/
└── server/
├── app.js
├── middlewares/
| └── payload-validator.js
└── schemas/
├── index.js
└── signup.js
Let's consider this server, that implements a single HTTP POST /signup route and uses the payloadValidator middleware:
const express = require('express');
const payloadValidator = require('./middlewares/payload-validator');
const app = express();
app.post('/signup', express.json(), payloadValidator('signup'), (req, res) => {
res.sendStatus(200);
});
app.listen(3000);
When sending this request, the server should responds with an HTTP 200 OK:
$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{"name":"John Doe","email":"johndoe@mail.com","password":"helloworld"}' \
127.0.0.1:3000/signup
OK
When sending this request, the server should respond with an HTTP 400 Bad Request caused by the missing name field in the request's payload:
$ curl -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"johndoe@mail.com","password":"helloworld"}' \
127.0.0.1:3000/signup
Bad Request
Summary
Here's a summary of what you've learned in this lesson:
- Validation refers to the process of ensuring the accuracy of the parsed data.
- The
Joi.object()method is used to create a validation schema. - The
validate()method of a schema is used to check arbitrary data against the format described by the schema.
Enjoying the courses?
I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:
Support BackendBrewery