DEV Community

loading...
Cover image for A Clean Approach to Using Express Validator

A Clean Approach to Using Express Validator

nedsoft profile image Chinedu Orie ・3 min read

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

Discussion (39)

pic
Editor guide
Collapse
gallib_net profile image
Alain

Very useful thanks!

For my purpose I just split validators.js in 2 parts, your validate const is for me a separate middleware in which I've also added a

req.matchedData = matchedData(req) 

useful to me to access to validated data in next middleware.

My validateRules middleware looks like:

import { matchedData, validationResult } from 'express-validator';

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

    if (errors.isEmpty()) {
        req.matchedData = matchedData(req);
        return next();
    }

    const extractedErrors = [];
    errors.array().map(err => extractedErrors.push({ [err.param]: err.msg }));

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

export default validateRules;
Collapse
arnabmunshi profile image
ARNAB MUNSHI

hi Alain

import { matchedData, validationResult } from 'express-validator';

This line not working for me. I don't know why.

But the following code is work for me.

import expressValidator from "express-validator";
const { body, validationResult } = expressValidator;
Collapse
nedsoft profile image
Chinedu Orie Author

That was insightful. Thanks for sharing!

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
pearsonhill profile image
PearsonHill • Edited

Hey Chinedu,

I echo sentiment of others regarding the endless search for finding a tutorial that made sense and was easy to implement! Thank you! Would I check for duplicates in the database in this file as well?

  • Pearson

PS: I found this:
express-validator.github.io/docs/c...

how would we incorporate it into what we have?

I'm sure your slammed with requests like this - no worries if you don't have the bandwidth to reply

Collapse
pearsonhill profile image
PearsonHill

hey Chinedu - kept searching and found my answer! I am fired up - see below if anyone else is interested:

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

const teamValidationRules = () => {

return [
// is name present
check('name'," a string 'name' is required.")
.notEmpty().isString()
.custom(
async (value, {req}) => {
const check = await Team.findOne({name: value})

           if(check){
            return Promise.reject('Name is already in use');
           }


        }),
Collapse
nedsoft profile image
Chinedu Orie Author

Great job, I just saw your comment, good to know you've been able to tackle it

Collapse
syed456 profile image
syed

Hi Chinedu,
I was looking exactly this way,
I want to know how the below line works
"app.post('/user', userValidationRules(), validate, (req, res) => {"

How userValidationRules() related to validate,
Thanks
Syed

Collapse
nedsoft profile image
Chinedu Orie Author

Hi Syed, the userValidationRules() is a helper which uses the express-validator methods to intercept the request and enforce the validation, express-validator methods pass the results of the validations to the request, the validate is a middleware which checks if the request contains errors injected by the express-validator methods; as middlewares do, if there's an error in the request object, it returns the error response, if there's no error in the request object, it calls the next function allowing the request to continue to the next stage.

Collapse
syed456 profile image
syed

Hi thanks for the reply
I'm sorry, I'm still in learning the middleware. You said userValidationRules() is helper, but not a middleware, since because of the braces ()?, how these two (userValidationRules() and valiadate) differ from each other in that place.
My understanding is, the helper userValidationRules(), executed immediately, and send the errors ,if any, to validate middleware 'validate'?.
If i want to combine those two into one single middleware, how can I achieve that?

Thanks
Syed

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

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
Collapse
atexzonate profile image
atexzonate

Nice post. Was struggling with this for a while until I saw your post.. Thank Omo Naija

Collapse
nedsoft profile image
Chinedu Orie Author

Haha, the Omo Naija part. Good to know you found it helpful ✌️

Collapse
consciousness_dev profile image
Ario Setiawan

My error value just "Invalid value", not described as example :
{
"errors": [
{
"username": "username must be an email"
},
{
"password": "password must be at least 5 chars long"
},
]
}

that is from validator library or from backend?

Collapse
nedsoft profile image
Chinedu Orie Author

If you didn't specify a custom error message, then it'll assume the default message from the library

Collapse
consciousness_dev profile image
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
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 Author

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
Collapse
nedsoft profile image
Chinedu Orie Author

Hi Mohammad, If I got your question clearly it means you're using server side rendering. The example above would serve well for an API where all responses are JSON objects. In your case, you return a view instead of a JSON object. So what you should do is to return a view then passing the error object as a data to the view.

Collapse
nbsamurai profile image
Subramani Simhachalam

Very useful info. Was trying to upgrade from the previous version of express-validator. Thanks

Collapse
thanhtruchuynh profile image
Truc Huynh

Thank you! This article is very useful !

Collapse
chidioguejiofor profile image
Chidiebere Ogujeiofor

Really appreciate this one. Was already trying to do something like this. Thanks for saving me some valuable time

Collapse
nedsoft profile image
Chinedu Orie Author

Glad you found it helpful

Collapse
onestica profile image
onestica

Simple and clear explanation. Thanks, this is what I'm looking for :)

Collapse
nishig20 profile image
nishiG-20

Thanks Chinedu

Collapse
kidmek profile image
Kidmek

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

Collapse
rufatn profile image
Rufat Nuriev

VERY USEFUL! THANK YOU!

nedsoft profile image
Chinedu Orie Author

Yeah. That's pretty much it. Good job!

Collapse
hvsharma63 profile image
Harshvardhan Sharma

Hello,
Just one question, How do you validate FormData non-file and file fields together?

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 ?