DEV Community

Cover image for A Clean Approach to Using Express Validator
Chinedu Orie
Chinedu Orie

Posted on

A Clean Approach to Using Express Validator

Express validator is one of the many npm packages for validating a request an express application.

I recently used express validator in a project and stumbled upon a few challenges which I'm going to share in this article.

Note: This article assumes that the reader already has an express project and only wants to implement validation using express validator. Hence, some details may be skipped.

When you visit the express validator docs, you'd notice the way the validator was used in the examples as shown in the snippet below:

// ...rest of the initial code omitted for simplicity.
const { check, validationResult } = require('express-validator')

app.post(
  '/user',
  [
    // username must be an email
    check('username').isEmail(),
    // password must be at least 5 chars long
    check('password').isLength({ min: 5 }),
  ],
  (req, res) => {
    // Finds the validation errors in this request and wraps them in an object with handy functions
    const errors = validationResult(req)
    if (!errors.isEmpty()) {
      return res.status(422).json({ errors: errors.array() })
    }

    User.create({
      username: req.body.username,
      password: req.body.password,
    }).then(user => res.json(user))
  }
)
Enter fullscreen mode Exit fullscreen mode

Looking at the snippet above, you'd notice that the validation is tightly coupled to the route definition. That pattern may be okay for a very simple use case but when usage scales, it'd be difficult for the codebase to be maintained and also it makes the route definition not readable.

In this article, I'll be showing how the validation above can be made more readable and easier to maintain.

Step 1

Create a file named validator.js
Inside the validator.js, we are going to add two functions, one of the functions will hold the validation rules, while the second will contain the function the does the actual validation.

Copy the snippet below into the validator.js

const { body, validationResult } = require('express-validator')
const userValidationRules = () => {
  return [
    // username must be an email
    body('username').isEmail(),
    // password must be at least 5 chars long
    body('password').isLength({ min: 5 }),
  ]
}

const validate = (req, res, next) => {
  const errors = validationResult(req)
  if (errors.isEmpty()) {
    return next()
  }
  const extractedErrors = []
  errors.array().map(err => extractedErrors.push({ [err.param]: err.msg }))

  return res.status(422).json({
    errors: extractedErrors,
  })
}

module.exports = {
  userValidationRules,
  validate,
}
Enter fullscreen mode Exit fullscreen mode

Step 2

Now re-writing the initial snippet above, we'd have:

const { userValidationRules, validate } = require('./validator.js')
app.post('/user', userValidationRules(), validate, (req, res) => {
  User.create({
    username: req.body.username,
    password: req.body.password,
  }).then(user => res.json(user))
})
Enter fullscreen mode Exit fullscreen mode

Now if you try to register a user without meeting the specification for the user data, the validation error response would look like shown below:

{
    "errors": [
        {
            "username": "username must be an email"
        },
        {
            "password": "password must be at least 5 chars long"
        },
    ]
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this method in place, you can define the validation rules for each route or module in a separate file as you may deem fit and then chain it with the validate middleware. That way the code looks much cleaner, easier to read and easier to maintain.

This article has a lot of assumptions and hence some details were skipped. However, if you do have any question, feel free to reach out to me.

You can read more about express validator on the official documentation website

This article was originally published on my blog

Latest comments (45)

Collapse
 
yassne profile image
yasu uo

Hell,Thanks For Shairng !
I want to just to know if someone use the validator.js library

Collapse
 
rick47curious profile image
Anuplab chatterjee

Awesome article. I have recently used this implementation in my clone application with slight tweeks.
Thanks for the share! Cheers 🥂

Collapse
 
imkivan profile image
imk-ivan

Great solution, thanks!

Collapse
 
drey profile image
Aduramimo Oludare

Hi Chinedu,

Was looking for a way to make a custom middleware for this validator and this solved my problem brilliantly!

Awesome work

Collapse
 
shanbiswas profile image
Santanu Biswas

I found a more simplistic approach of doing this on Stackoverflow.

// validator.js
const { check, validationResult } = require('express-validator')

exports.validateUser = [
check('name')
    .trim()
    .escape()
    .not()
    .isEmpty()
    .withMessage('Name can not be empty!')
    .bail()
    .isLength({min: 3})
    .withMessage('Minimum 3 characters required!')
    .bail(),

(req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty())
    return res.status(200).json({errors: errors.array()});
    next();
},
];


// router.js
const validateUser = require('../validator)
router.post('/users/create', validateUser, UserController.create)

// UserController.js
async create(req, res) {
     // do stuff
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mirzasetiyono profile image
Mirza Setiyono

thank you!

Collapse
 
farzinkhaleghian profile image
Farzin Khaleghian

In this function if we want to support multiple validation rules and make a condition based on one key in the request body, do you have any ideas? In the other hand how we can pass req in this function?

Collapse
 
pprachit09 profile image
Prachit Suhas Patil

Thanks for sharing! Very useful.

Collapse
 
nishig20 profile image
nishiG-20

Thanks Chinedu

Collapse
 
veera_zaro profile image
Veera

userValidationRules() why do we need to use parentheses to call this function in the route. since this is a callback right ?

Collapse
 
damien1990 profile image
Damien1990

Love this approach to validation but wondering how I would compare the value of one field to another with it? For example password and confirmpassword. Any help would be appreciated.

Collapse
 
nedsoft profile image
Chinedu Orie

You can use a custom rule

See example from the docs:

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

app.post('/user', body('passwordConfirmation').custom((value, { req }) => {
  if (value !== req.body.password) {
    throw new Error('Password confirmation does not match password');
  }

  // Indicates the success of this synchronous custom validator
  return true;
}), (req, res) => {
  // Handle the request
});
Collapse
 
damien1990 profile image
Damien1990

Thank you, it worked beautifully. I'm guessing you can use a similar approach to checking for 'unique' emails/usernames by requiring the model in the validator class, using the value to search for existing emails/usernames in the database and if one is returned, to throw an error?

Thread Thread
 
nedsoft profile image
Chinedu Orie

yes

Collapse
 
sanwar1 profile image
sanwar1

Thank you Chinedu. Just what I was looking for. Very elegant. However, this is just for one API handle. What if there are multiple request handles and each of them with multiple parameters?

Should I create a separate layer for validations for a real-world application??

Collapse
 
geepalik profile image
Gil Palikaras • Edited

Hi Chinedu,

Thank you for the solution, looks great and works great.
I am using PHPStorm/WebStorm and when I implemented this solution, the editor shows warning (yellow underline):
Argument types do not match parameters

const {studentValidationRules, validate} = require('../util/dataValidator');
router.post('/student', studentValidationRules(), validate ,studentController.createStudent);

error

I thought I was using correctly the pattern of adding middleware to my routes, did I do something wrong?

Collapse
 
kidmek profile image
Kidmek

Thank you so much! There are so many articles which are misleading.

Collapse
 
reak45 profile image
Reak45

Great post! However I have a question. Can this be merged in a single middleware? Instead of two functions?

Collapse
 
nedsoft profile image
Chinedu Orie • Edited

Certainly yes, I've recently made an improvement to it in my personal codes. The validator takes schema as a param. I found it cleaner and I'd update the article ASAP. Below is what it looks like:

const { body, validationResult} = require('express-validator');

const validate = (schemas)  => {
    return async (req, res, next) => {
      await Promise.all(schemas.map((schema) => schema.run(req)));

      const result = validationResult(req);
      if (result.isEmpty()) {
        return next();
      }

      const errors = result.array();
      return  res.send(errors)
    };
  }
 const exampleSchema = [
   body('foo', 'The foo field is required').notEmpty(),
   ...
];

router.post('/foos', validate(exampleSchema), fooHandler);

Enter fullscreen mode Exit fullscreen mode
Collapse
 
leblancmeneses profile image
Leblanc Meneses

How would you implement mutually exclusive properties like google maps geocoding?
can be either: address or latlng but not both

github.com/express-validator/expre...

seems oneOf is middleware not a ValidationChain[]. I'm handling it manually using a custom rule because that allows me to continue using similar "validate" middleware, although, what else are we missing by having this convenience "validate" wrapper .

Great writeup!

Collapse
 
reak45 profile image
Reak45

This is just awesome! Thank you so much!

Thread Thread
 
nedsoft profile image
Chinedu Orie

Anytime!