Schema based validation using express-validator in Node.js

jayeshchoudhary

Jayesh Choudhary

Posted on January 9, 2022

Schema based validation using express-validator in Node.js

When working with Node.js it is very common to receive data in request (body, query, params), and based on that data we perform some operations on DB and return the results.

Since the data will be coming from external resources like Client-side UI (browsers), programs that consume our API, Postman (API testing client) etc. hence we need to make sure that the data we are receiving should be properly validated before passing it to the controller or DB.

In this tutorial, we will be taking a look at the best and easiest way to validate request data using express-validator package

Not validating data can lead to unwanted data, program crashing, malicious hacker attack so it is always recommended to validate data first before doing any operation on it

💡 all the examples included in the tutorial are available on my github repo

Basic Project Setup

In this tutorial, we will be building an express.js app with some API endpoints POST - /api/user and validate incoming req data

# Create the project folder
$ mkdir express-validator-example

# Navigate into the project folder
$ cd express-validator-example

# Initialize project
$ npm init -y

# install express
$ npm install express
Enter fullscreen mode Exit fullscreen mode

Project structure

we will be following best practice by using modular approach where everything is placed in a different file, this will make our code structured and maintainable

image.png

index.js

const express = require("express");

const app = express();
const PORT = 3000;
const userRouter = require("./routes/user.router");

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// routes middleware
app.use("/api/user", userRouter);

app.listen(PORT, () => console.log("Server listening on port", PORT));
Enter fullscreen mode Exit fullscreen mode

routes/user.router.js

const router = require("express").Router();
const UserController = require("../controllers/user.controller");
const { userDataValidate } = require("../validations/user.validation");

router.post("/", userDataValidate, UserController.addUser);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

controllers/user.controller.js

const addUser = (req, res, next) => {
  const userData = {
    userName: req.body.userName,
    password: req.body.password,
    email: req.body.email,
  };

  try {
    // save data to DB
    User.create(userData);

    res.json({ success: true });
  } catch (err) {
    next(err);
  }
};

module.exports = { addUser };
Enter fullscreen mode Exit fullscreen mode

validations/user.validation.js

const userDataValidate = (req, res, next) => {
  // todo
};

module.exports = { userDataValidate };
Enter fullscreen mode Exit fullscreen mode

Traditional way of data validation

let's validate user data received by hitting /api/user without using any external libraries

user.validation.js

const userDataValidate = (req, res, next) => {
  if (!req.body.userName) {
    throw Error("username is required");
  }
  if (!req.body.password) {
    throw Error("password is required");
  }
  if (req.body.password.length < 5) {
    throw Error("password should have atleast 5 characters");
  }
  if (!isValidEmail()) {
    throw Error("provide valid email");
  }
  // .... and so on
};

module.exports = { userDataValidate };
Enter fullscreen mode Exit fullscreen mode

as you can see there are lot of validation if() checks. and if our api had 10-15 keys then the validation function will going to be very long and prone to errors

Introduction to express-validator

According to express-validator docs

express-validator is a set of
express.js middlewares that wraps validator.js validator and sanitizer functions.

express-validator makes data validation very simple and easy to maintain. also it is the most popular choice in node.js for validations

installation

npm install --save express-validator
Enter fullscreen mode Exit fullscreen mode

Usage

Since each validation rule in express-validator is separate middleware, hence we can pass an array of validation rules to middleware in user.router.js

let's write user validation of data on various fields

add below code to user.validation.js

const { body } = require("express-validator");

const userDataValidateChainMethod = [
  body("userName")
    .exists({ checkFalsy: true })
    .withMessage("User name is required")
    .isString()
    .withMessage("User name should be string"),
  body("password")
    .exists()
    .withMessage("Password is required")
    .isString()
    .withMessage("Password should be string")
    .isLength({ min: 5 })
    .withMessage("Password should be at least 5 characters"),
  body("email").optional().isEmail().withMessage("Provide valid email"),
  body("gender")
    .optional()
    .isString()
    .withMessage("Gender should be string")
    .isIn(["Male", "Female", "Other"])
    .withMessage("Gender value is invalid"),
  body("dateOfBirth")
    .optional()
    .isDate()
    .withMessage("DOB should be valid date"),
  body("phoneNumber")
    .optional()
    .isString()
    .withMessage("phone number should be string")
    .custom((value) => {
      if (value.length !== 10) {
        return Promise.reject("Phone number should be 10 digits");
      } else {
        return true;
      }
    }),
];
Enter fullscreen mode Exit fullscreen mode

Explanation:

express-validator provides chainable functions which we add as many validation rules as we want

In the code above we have used below validation middleware

  • body(): this will only validate req.body fields (if you want to validate param, query of request then param(), query() are also available) there is also check() available which will search for key in whole req object but only for req.body use body()
  • exists(): for required fields (makes field compulsory to include) there is also checkFalsy: true option available which also check if a value should not contain falsy value like "", null, undefined
  • withMessage(): custom message to display when validation fails
  • isString(): checks if value is string
  • isDate(): checks if it is valid date
  • optional(): value is optional
  • isIn(): check if input value contains one of value present in array.
  • isEmail(): checks for valid email id
  • custom(): write a custom validator for your own needs (you can also write async DB lookup validations here)
  • isLength({min: 2, max: 10}): check for min and max characters in value

Other common validator's

  • isNumeric(): checks if value is number
  • bail(): Stops running validations if any of the previous ones have failed.
  • isMobilePhone(): checks if input is valid phone number

💡 To explore other validator functions refer to validator.js

Now using userDataValidateChainableAPI, your /routes/user.router.js will be like this:

user.router.js

router.post("/", userDataValidateChainMethod, UserController.addUser);
Enter fullscreen mode Exit fullscreen mode

Error handing

To get the errors from user validation use validationResult() from the express-validator

In the user controller we will check the errors from the validation. if there are any then return all the errors

user.controller.js

const { validationResult } = require("express-validator");

const addUser = (req, res, next) => {
  try {
    const errors = validationResult(req);

    // if there is error then return Error
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array(),
      });
    }

    // save data to DB
    User.create(req.body);

    res.json({ success: true });
  } catch (err) {
    next(err);
  }
};

module.exports = { addUser };
Enter fullscreen mode Exit fullscreen mode

Error Object

When the POST - /api/user is hit then we will get errors (if we have not provided req.body as required by our schema)

Below is how the error object will look like

{
    "success": false,
    "errors": [
        {
            "value": "tet",
            "msg": "Password should be at least 5 characters",
            "param": "password",
            "location": "body"
        },
        {
            "value": "test.gmail",
            "msg": "Provide valid email",
            "param": "email",
            "location": "body"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Schama based validation

The chain api for validation provided by express-validator is great but it can also get very hard to read if a single field has many validations. then a single validation middleware will have chain function hell

To solve this problem there is also schema-based validation in express-validator. this offers a clear approach where instead of chaining new function for new validation we have an object with key and values as validation schema at one place.

checkSchema() takes schema object as parameter and will be placed inside our user router middleware

let's create same validation in schema-based validation

user.validation.js

const userDataValidateSchemaBased = checkSchema({
  userName: {
    exists: {
      errorMessage: "User name is required",
      options: { checkFalsy: true },
    },
    isString: { errorMessage: "User name should be string" },
  },
  password: {
    exists: { errorMessage: "Password is required" },
    isString: { errorMessage: "password should be string" },
    isLength: {
      options: { min: 5 },
      errorMessage: "Password should be at least 5 characters",
    },
  },
  email: {
    isEmail: { errorMessage: "Please provide valid email" },
  },
  gender: {
    isString: { errorMessage: "Gender should be string" },
    isIn: {
      options: [["Male", "Female", "Other"]],
      errorMessage: "Gender is invalid",
    },
  },
  dateOfBirth: {
    isDate: { errorMessage: "DOB should be string" },
  },
  phoneNumber: {
    isString: { errorMessage: "phone number should be string" },
    options: (value) => {
      value.length === 10;
    },
    errorMessage: "Phone number should be 10 digits",
  },
});
Enter fullscreen mode Exit fullscreen mode

To use schema based object our user.router.js will look like this

checkSchema() is used for schema validation

user.router.js

router.post(
  "/schama-based",
  checkSchema(userDataValidateSchemaBased),
  UserController.addUser
);
Enter fullscreen mode Exit fullscreen mode

Testing /api/user using Postman

run project using npm run start
postman test

Conclusion

  • successfully used express-validator package to easily validate input for Node.js apps
  • Schema based validation is even faster and convenient
  • Also can sanitize data for improved security

References

[1] Form Data Validation in Node.js with express-validator
[2] How to make input validation simple and clean in your Express.js app

Links

  1. express-validator official docs
  2. validator.js docs
  3. Find all the code examples in my github repo

Buy Me A Coffee

💖 💪 🙅 🚩
jayeshchoudhary
Jayesh Choudhary

Posted on January 9, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related