Schema Validation with Yup and Express.js

I feel lucky to live in an era where we have so many alternatives to do the same thing. A lot of people criticize this, but I think it's amazing to have libraries that do the same thing but with different approaches. I think this helps the programmer to implement a project with something that follows his reasoning.

But today this is not going to be the subject of the article. Today I am going to teach you how to make a validation system using Yup together with Express.js.

In the past I had written an article about how to do exactly the same thing we are going to do today, except using Joi, if you want to read the article click here.

If you've used Joi in the past you'll feel comfortable using Yup, both libraries are quite similar.

However I find Yup more intuitive, with a cleaner Api and at the same time it offers a great development experience.

And if you're one of those people who cares a lot about the size of your project's bundle, let me tell you that Yup is much lighter than Joi.

I hope I caught your attention, so now let's move on to the code.

Let's code

As always, let's install the necessary dependencies first.

npm i express yup --save

Now we need to create a simple api in Express, similar to this:

const express = require("express");

const app = express();

app.use(express.json());

app.get("/", (req, res) => {
  return res.json({ message: "Validation with Yup 👊" });
});

const start = (port) => {
  try {
    app.listen(port, () => {
      console.log(`Api running at: http://localhost:${port}`);
    });
  } catch (err) {
    console.error(err);
    process.exit();
  }
};
start(3333);

Now that we have the foundation of our project, we can start using Yup. First we will create a schema based on the following JSON (which will be the body of our http request):

{
  "title": "This is the title",
  "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
  "contact": "[email protected]",
  "url": "https://safe-link.me"
}

Now let's create a new route in our api, in which we'll return the object data from the http request body and we'll still return the id from the params.

app.post("/create/:id", (req, res) => {
  return res.json({ body: req.body, id: req.params.id });
});

Now with the route created, we only need two things, a middleware for validating the schema and the schema itself. So first we'll create our schema, not forgetting to import Yup into our project.

const yup = require("yup");

// Hidden for simplicity

const linkSchema = yup.object({
  body: yup.object({
    url: yup.string().url().required(),
    title: yup.string().min(8).max(32).required(),
    content: yup.string().min(8).max(255).required(),
    contact: yup.string().email().required(),
  }),
  params: yup.object({
    id: yup.number().required(),
  }),
});

As we can see, we will validate the body of our http request and its parameters. However, if in your projects you also want to use query strings, you can also validate them.

This time I'm going to take a different approach, because Yup allows me to do that. This is because I will want to reuse the middleware several times and I just want it to validate the schema that I pass in the arguments.

So this way we will only write the middleware once and we will only have to create several individual schemas (just like we did with linkSchema).

Moving now to the creation of middleware so that everything I said earlier starts to make sense. We'll call the middleware validate.

const validate = (schema) => async (req, res, next) => {
  // logic goes here
};

As you can see, in middleware we will receive the schema as a function argument, after that we will validate it, if everything is correct, we will have access to the controller.

const validate = (schema) => async (req, res, next) => {
  try {
    await schema.validate({
      body: req.body,
      query: req.query,
      params: req.params,
    });
    return next();
  } catch (err) {
    // More logic goes here
  }
};

As you can see in the code, the middleware will be ready to validate the body, the params and the query strings, which makes it extremely flexible in this way.

Now, to finish the middleware, just return the respective error that occurred during the schema validation, as follows:

const validate = (schema) => async (req, res, next) => {
  try {
    await schema.validate({
      body: req.body,
      query: req.query,
      params: req.params,
    });
    return next();
  } catch (err) {
    return res.status(500).json({ type: err.name, message: err.message });
  }
};

Now with the schema and the middleware created, just add it to the route, as follows:

app.post("/create/:id", validate(linkSchema), (req, res) => {
  return res.json({ body: req.body, id: req.params.id });
});

Now, if you are going to send an http request to the endpoint, you can see that Yup will already do the respective validation of each of the fields, according to the rules stipulated by us in the schema.

And if everything is correct, we will see the data that we send in the http request in the response from the endpoint's response. However, if one of the fields is incorrectly filled, we will only see the error that occurred.

The final code is as follows:

const express = require("express");
const yup = require("yup");

const app = express();

app.use(express.json());

const linkSchema = yup.object({
  body: yup.object({
    url: yup.string().url().required(),
    title: yup.string().min(8).max(32).required(),
    content: yup.string().min(8).max(255).required(),
    contact: yup.string().email().required(),
  }),
  params: yup.object({
    id: yup.number().required(),
  }),
});

const validate = (schema) => async (req, res, next) => {
  try {
    await schema.validate({
      body: req.body,
      query: req.query,
      params: req.params,
    });
    return next();
  } catch (err) {
    return res.status(500).json({ type: err.name, message: err.message });
  }
};

app.get("/", (req, res) => {
  return res.json({ message: "Validation with Yup 👊" });
});

app.post("/create/:id", validate(linkSchema), (req, res) => {
  return res.json({ body: req.body, id: req.params.id });
});

const start = (port) => {
  try {
    app.listen(port, () => {
      console.log(`Api running at: http://localhost:${port}`);
    });
  } catch (err) {
    console.error(err);
    process.exit();
  }
};
start(3333);

I hope I explained it in a simple way and that it helped you in your projects.

What about you?

What schema validation do you use in your projects?

20