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

Top comments (45)

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) 
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
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

That was insightful. Thanks for sharing!

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

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

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
 
imkivan profile image
imk-ivan

Great solution, thanks!

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

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

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

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!

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

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

Collapse
 
consciousness_dev profile image
Ario Setiawan
Collapse
 
nedsoft profile image
Chinedu Orie

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.