How to make input validation simple and clean in your Express.js app
This tutorial requires prior knowledge of using the expressjs framework
Why do we need server-side validation?
- Your client side validation is not enough and it may be subverted
- More prone to Man in middle attacks, and the server should never trust the client-side
- A 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 or
- you want to avoid using
if (req.body.head)
orif (req.params.isCool)
in your main controller function where you define business logic
In this tutorial, you’ll learn how to validate input in an Express.js app using an open source and popular module called express-validator.
Introduction to express-validator
The definition on Github says:
express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
The module implements five important API’s:
- Check API
- Filter API
- Sanitization chain API
- Validation chain API
- Validation Result API
Let's take a look at a basic user route
without any validation module to create a 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. Let's 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) }}
The above code is just a basic example of validating fields on your own.
You can handle some validations in your user model using Mongoose. For best practices, we want to make sure validation happens before business logic.
express-validator will take care of all these validations and the 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 this:
router.post(
'/',
userController.validate('createUser'), userController.createUser,
)
Here userController.validate
is a middleware function which is explained below. It accepts the method
name for which the validation will be used.
Let’s create a middleware function validate()
in our/controllers/user.js
:
const { body } = require('express-validator/check')exports.validate = (method) => {switch (method) {
case 'createUser': { return [
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 to this article to know more about function definition and its use.
The body
function will only validate req.body
and takes two arguments. First is the property name
. Second is your custom message
that will be shown if validation fails. If you don’t provide a custom message, then the default message will be used.
As you can see, for a required
field we are using the .exists()
method. We are using .optional()
for an optional
field. Similarly isEmail()
isInt()
is used to validate email
and integer
.
If you want an input field to include only certain values, then you can use .isIn([])
. This takes an array
of values, and if you receive values other than the above, then an error will be thrown.
For example, the status field in the above code snippet can only have an enabled
or disabled
value. If you provide any value other than that, an error will be thrown.
In /controllers/user.js
let’s write acreateUser
function where you can write business logic. It will be called after validate()
with the result of the 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)}
Are you 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 the below:
{
param: "field name",
msg: "error message", value: "<field input value>"
}
But how would you return .msg
from that object in response to your API?
Let’s write a 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 the getValidationResult
method. The outer function takes the next
middleware.
So if both userName
and email
failed to satisfy the validation, then each error returned by the .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 validationHandler
will parse and pass them on to the next middleware. If there is no error
, you will receive an empty array
which satisfies the first condition in the function.
As you can see, this module really helps us take care of most of the validations on its own. It maintains code quality as well, and focuses mainly on business logic.
This was the introduction to input validation using the express-validator module and check out how to validate an array of the item and make your own custom validation in Part 2 of this series.
I have tried my best and hope I covered enough to explain it in detail so that you can get started.
If you encounter any problems, feel free to get in touch or comment below.
I would be happy to help :)
Follow Shailesh Shekhawat to get notified whenever I published a new post.
Don’t hesitate to clap if you considered this a worthwhile read!
Originally published at 101node.io on September 2, 2018.