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))
}
)
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,
}
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))
})
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"
},
]
}
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)
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
useful to me to access to validated data in next middleware.
My validateRules middleware looks like:
hi Alain
This line not working for me. I don't know why.
But the following code is work for me.
That was insightful. Thanks for sharing!
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?
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
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})
Great job, I just saw your comment, good to know you've been able to tackle it
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
I thought I was using correctly the pattern of adding middleware to my routes, did I do something wrong?
Great solution, thanks!
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
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 thenext
function allowing the request to continue to the next stage.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
I found a more simplistic approach of doing this on Stackoverflow.
Nice post. Was struggling with this for a while until I saw your post.. Thank Omo Naija
Haha, the Omo Naija part. Good to know you found it helpful ✌️
Great post! However I have a question. Can this be merged in a single middleware? Instead of two functions?
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:
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!
This is just awesome! Thank you so much!
Anytime!
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?
If you didn't specify a custom error message, then it'll assume the default message from the library
Ok i got it, express-validator.github.io/docs/c... thanks
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.