top of page

Schema based validation using express-validator in Node.js

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

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

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

Schema based validation using express-validator in Node.js

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));

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;

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 };

validations/user.validation.js

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

module.exports = { userDataValidate };

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 };
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

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;
      }
    }),
];

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);

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 };

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"
        }
    ]
}

Schema 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",
  },
});

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
);

Testing /api/user using Postman

run project using npm run start

Schema based validation using express-validator in Node.js

References

Links

  1. express-validator official docs

  2. validator.js docs

Related Posts

See All

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Stationary photo

Be the first to know

Subscribe to our newsletter to receive news and updates.

Thanks for submitting!

Follow us
bottom of page