Web forms have become an essential part of web applications. And as soon as the data is valid, we know that it is safe, because we define a set of rules to be followed, in order to have a standardization. This standardization can be from the type of data, up to the number of characters.
Data validation can be done on the client side and on the server side. From what I usually see on the internet, most people implement form validation just on the client side. But in this article I will talk about a library that can be used in the backend.
The library in question is called Joi, in my opinion it is the easiest validation library to implement, it is super popular and there are still several ways to implement it.
I believe that a lot of people must be asking why we will use a validation library since many ORMs let us create restrictions for their schemes and models. Basically, when using a validation library, it is easier to validate the data before accessing the database.
In the example of this article we will create middleware that will validate the data before reaching the controller (where the application logic is supposed to be). In this way, no operation is performed that is sensitive in our application, nor do we have any interaction with external resources (such as caching, database, etc).
What is Joi?
Joi is a validation library that allows you to build schemas to validate JavaScript objects. Basically Joi provides methods to easily validate strings, booleans, integers, email addresses, phone numbers, among others.
Imagine that this is the object sent from the frontend to the backend:
{
"title": "This is supposed to be a title",
"content": "There should be some content here."
}
But we know that the title must have a minimum of 8 characters and a maximum of 30. While the content must have a minimum of 24 characters and a maximum of 255. And both are strings and are required.
The Joi schema equivalent to our object would be the following:
const schema = Joi.object({
title: Joi.string().min(8).max(30).required(),
content: Joi.string().min(24).max(255).required(),
});
One of Joi's strong points is its easy readability. Even if it is your first time to define a schema using Joi, I believe it is intuitive enough to start playing with this library.
Now that we have a basic idea of everything, let's move on to our example.
Let's code
As we will always create a basic api, in this case pretend that we have a route that will add a new article to the database. And even if the data has been validated on the client side, it is always a good idea to validate again.
But first we will install the following dependencies:
npm i express joi
Then we will create our simple Api:
const express = require("express");
const app = express();
app.use(express.json());
app.post("/", (req, res) => {
return res.json({ id: 1, ...req.body, createdAt: new Date() });
});
const start = (port) => {
try {
app.listen(port, () => {
console.log(`Api running at: http://localhost:${port}`);
});
} catch (error) {
console.error(error);
process.exit();
}
};
start(4000);
Now we are going to create our middleware that will be responsible for validating the data. If the data is within our standards, access to the controller will be possible, otherwise it will be denied and an error message will be displayed. Let's name our middleware policy:
const policy = (req, res, next) => {
// Logic goes here
}
After creating the middleware, we have to define our schema, in this case I will reuse the schema we created earlier.
const policy = (req, res, next) => {
const schema = Joi.object({
title: Joi.string().min(8).max(30).required(),
content: Joi.string().min(24).max(255).required(),
});
// More logic goes here
}
With the schema defined, we now have to access the object's data, so we will search for it in the body.
const policy = (req, res, next) => {
const schema = Joi.object({
title: Joi.string().min(8).max(30).required(),
content: Joi.string().min(24).max(255).required(),
});
const { title, content } = req.body
// More logic goes here
}
Then we have to pass the same fields through Joi's validation method using our schema and we will get the error.
const policy = (req, res, next) => {
const schema = Joi.object({
title: Joi.string().min(8).max(30).required(),
content: Joi.string().min(24).max(255).required(),
});
const { title, content } = req.body
const { error } = schema.validate({ title, content });
// More logic goes here
}
First, we will want to know if an error occurred during data validation. If one has occurred, we will want to know which of the keys of the object were and what is the message given by Joi. For this we will use a switch and depending on the key, we will return the corresponding message. If there is no error, we will allow access to the controller.
// Hidden for simplicity
if (error) {
switch (error.details[0].context.key) {
case "title":
res.status(500).json({ message: error.details[0].message });
break;
case "content":
res.status(500).json({ message: error.details[0].message });
break;
default:
res.status(500).json({ message: "An error occurred." });
break;
}
}
return next();
Then go to our route and add our middleware before the controller. Like this:
app.post("/", policy, (req, res) => {
return res.json({ id: 1, ...req.body, createdAt: new Date() });
});
The final code should look like the following:
const express = require("express");
const Joi = require("joi");
const app = express();
app.use(express.json());
const policy = (req, res, next) => {
const schema = Joi.object({
title: Joi.string().min(8).max(30).required(),
content: Joi.string().min(24).max(255).required(),
});
const { title, content } = req.body;
const { error } = schema.validate({ title, content });
if (error) {
switch (error.details[0].context.key) {
case "title":
res.status(500).json({ message: error.details[0].message });
break;
case "content":
res.status(500).json({ message: error.details[0].message });
break;
default:
res.status(500).json({ message: "An error occurred." });
break;
}
}
return next();
};
app.post("/", policy, (req, res) => {
return res.json({ id: 1, ...req.body, createdAt: new Date() });
});
const start = (port) => {
try {
app.listen(port, () => {
console.log(`Api running at: http://localhost:${port}`);
});
} catch (error) {
console.error(error);
process.exit();
}
};
start(4000);
Now I recommend visiting Joi's documentation because it is possible to do many more things than what was done here in the article.
What about you?
Have you used data validation schemes in your Node.js projects?
Top comments (1)
Francisco, thank you for this article.
I'm actually trying to figure out how to use joi to validate an image field..