DEV Community

loading...
Cover image for Schema Validation with Joi and Node.js

Schema Validation with Joi and Node.js

Francisco Mendes
I don't know about you but I'm just getting started. 🍩
・5 min read

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."
}
Enter fullscreen mode Exit fullscreen mode

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(),
 });
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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() });
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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?

Discussion (0)