How to make input validation in Express.js app simple and clean

This tutorial require prior knowledge of using expressjs framework

Why do we need server-side validation?

  • Your client side validation is not enough and it maybe subverted
  • More prone to Man in middle attack and server should never trust client side
  • User can turn off client side javascript validation and manipulate the data

If you have been building web applications using an express framework or any other node.js framework, validation plays a crucial role in any web app which requires you to validate the request body param query

Writing your own middleware function can be cumbersome if you want to move fast while maintaining the quality of code and to avoid usingif (req.body.head) or if (req.params.isCool) in your main controller function where you define business logic.

In this tutorial you’ll learn how to validate input in express.js app using an open source and popular module express-validator

Introduction to express-validator

Definition on github says:

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

Module implements 5 important API’s

  • Check API
  • Filter API
  • Sanitization chain API
  • Validation chain API
  • Validation Result API

Lets take a look at basic user route without any validation module to create user: /route/user.js

/**
* @api {post} /api/user Create user
* @apiName Create new user
* @apiPermission admin
* @apiGroup User
*
* @apiParam  {String} [userName] username
* @apiParam  {String} [email] Email
* @apiParam  {String} [phone] Phone number
* @apiParam  {String} [status] Status
*
* @apiSuccess (200) {Object} mixed `User` object
*/
router.post('/', userController.createUser)

Now in user controller /controllers/user.js

const User = require('./models/user')
exports.createUser = (req, res, next) => {
  /** Here you need to validate user input. 
   Lets say only Name and email are required field
 */
  
  const { userName, email, phone, status } = req.body
  if (userName && email &&  isValidEmail(email)) { 
    
    // isValidEmail is some custom email function to validate email which you might need write on your own or use npm module
    User.create({
      userName,
      email,
      phone,
      status,   
    })
    .then(user => res.json(user))
    .catch(next)
  }
}

Above code is just a basic example for validating fields on your own

You can handle some validations in your user model using mongoose but for the best practice we want to make sure validation happens before business logic.

express-validator will take care of all these validations and sanitization of inputs as well 

Installation

npm install --save express-validator

Include module in your main server.js file

const express = require('express')
const bodyParser = require('body-parser')
const expressValidator = require('express-validator')
const app = express();
const router = express.Router()
app.use(bodyParser.json())
app.use(expressValidator())
app.use('/api', router)

Now using express-validator your /routes/user.js will be like: 

router.post(
  '/', 
  userController.validate('createUser'), 
  userController.createUser,
)

Here userController.validate is a middleware function which acceptsmethodname for which validation will be used and in that function we will use express-validator API’s below:

Let’s create validate middleware function in our/controllers/user.js  

const { body } = require('express-validator/check')
exports.validate = (method) => {
switch (method) {
    case 'createUser': {
      body('userName', 'userName doesn't exists').exists(),
      body('email', 'Invalid email').exists().isEmail(),
      body('phone').optional().isInt(),
      body('status').optional().isIn(['enabled', 'disabled']),
    }
  }
}

Please refer : Check API to know more about function definition and its use

body function will only validate req.body and takes two arguments first is the property nameand second is your custom message that will be shown if validation fails, if you don’t provide custom message then default message will be used

As you can see for required field we are using .exists() method and .optiona()for optional field , similarly isEmail() isInt() to validate email and integer . 

If you want input field to include only certain values then you can use .isIn([]) which takes array of values and if you receive values other than above then error will be thrown

For example: status field in above code snippet can only have enabled or disabled value, if you provide any value other than that, error will be thrown

In /controllers/user.js let’s write createUser function which will be called after validate()with the result of validations.

exports.createUser = (req, res, next) => {
   req
   .getValidationResult() // to get the result of above validate fn
   .then(validationHandler())
   .then(() => {
      const { userName, email, phone, status } = req.body
      
      User.create({
        userName,
        email,
        phone,
        status,   
      })
      .then(user => res.json(user))
   })
   .catch(next)
}

If you are wondering what is req.getValidationResult()?

It is a new function used by Express-validator. This function returns an object if there are any errors in validation. The object looks like:

{
  param: "field name", 
  msg: "error message", 
  value: "<field input value>"
}

But how you would return .msg from that object in response to your API ?

Let’s write handler function for that

const validationHandler = next => result => {
  if (result.isEmpty()) return
  if (!next)
    throw new Error(
      result.array().map(i => `'${i.param}' has ${i.msg}`).join(' ')
    )
else
  return next(
    new Error(
     result.array().map(i => `'${i.param}' has ${i.msg}`).join('')
    )
  )
}

if you feel like this please hang on!!

Now back to our validation handler function which is a curry function.

The inner function takes the result provided by getValidationResult method and outer function takes the next middleware.

So if bothuserName and email failed to satisfy the validation then each error returned by .array() method has the following format by default:

{   
  "msg": "The error message",   
  "param": "param name",   
  "value": "param value",   
// Location of the param that generated this error.   // It's either body, query, params, cookies or headers.   
  "location": "body",    
// nestedErrors only exist when using the oneOf function
  "nestedErrors": [{ ... }] 
}

So if there are any errors then validationHandlerwill parse and pass on to next middleware and if there is no error you will receive an empty array which satisfies first condition in function.

As you can see this module really helps to take care of most of the validations on its own and help us to maintain code quality as well and focus mainly on business logic.

This was the introduction to input validation using the express-validator module and I have tried my best and hope I covered enough to explain in details so that you can get started.

if you encounter any problem feel free to get in touch or comment below.
I would be happy to help 🙂

Spread the love
  •  
  •  
  •  
  •  
  •  
  •  
shekhawat642
 

Click Here to Leave a Comment Below 0 comments

Leave a Reply: