When working with Node.js it is very common to receive data in request (body, query, params), and based on that data we perform some operations on DB and return the results.
Since the data will be coming from external resources like Client-side UI (browsers), programs that consume our API, Postman (API testing client) etc. hence we need to make sure that the data we are receiving should be properly validated before passing it to the controller or DB.
In this tutorial, we will be taking a look at the best and easiest way to validate request data using express-validator package.
Not validating data can lead to unwanted data, program crashing, malicious hacker attack so it is always recommended to validate data first before doing any operation on it
Basic Project Setup
In this tutorial, we will be building an express.js app with some API endpoints POST - /api/user and validate incoming req data
# Create the project folder
$ mkdir express-validator-example
# Navigate into the project folder
$ cd express-validator-example
# Initialize project
$ npm init -y
# install express
$ npm install express
Project structure
we will be following best practice by using modular approach where everything is placed in a different file, this will make our code structured and maintainable
index.js
const express = require("express");
const app = express();
const PORT = 3000;
const userRouter = require("./routes/user.router");
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// routes middleware
app.use("/api/user", userRouter);
app.listen(PORT, () => console.log("Server listening on port", PORT));
routes/user.router.js
const router = require("express").Router();
const UserController = require("../controllers/user.controller");
const { userDataValidate } = require("../validations/user.validation");
router.post("/", userDataValidate, UserController.addUser);
module.exports = router;
controllers/user.controller.js
const addUser = (req, res, next) => {
const userData = {
userName: req.body.userName,
password: req.body.password,
email: req.body.email,
};
try {
// save data to DB
User.create(userData);
res.json({ success: true });
} catch (err) {
next(err);
}
};
module.exports = { addUser };
validations/user.validation.js
const userDataValidate = (req, res, next) => {
// todo
};
module.exports = { userDataValidate };
Traditional way of data validation
let's validate user data received by hitting /api/user without using any external libraries
user.validation.js
const userDataValidate = (req, res, next) => {
if (!req.body.userName) {
throw Error("username is required");
}
if (!req.body.password) {
throw Error("password is required");
}
if (req.body.password.length < 5) {
throw Error("password should have atleast 5 characters");
}
if (!isValidEmail()) {
throw Error("provide valid email");
}
// .... and so on
};
module.exports = { userDataValidate };
as you can see there are lot of validation if() checks. and if our api had 10-15 keys then the validation function will going to be very long and prone to errors
Introduction to express-validator
According to express-validator docs
express-validator is a set of express.js middlewares that wraps validator.js validator and sanitizer functions.
express-validator makes data validation very simple and easy to maintain. also it is the most popular choice in node.js for validations
Installation
$ npm install --save express-validator
Usage
Since each validation rule in express-validator is separate middleware, hence we can pass an array of validation rules to middleware in user.router.js
let's write user validation of data on various fields
add below code to user.validation.js
const { body } = require("express-validator");
const userDataValidateChainMethod = [
body("userName")
.exists({ checkFalsy: true })
.withMessage("User name is required")
.isString()
.withMessage("User name should be string"),
body("password")
.exists()
.withMessage("Password is required")
.isString()
.withMessage("Password should be string")
.isLength({ min: 5 })
.withMessage("Password should be at least 5 characters"),
body("email").optional().isEmail().withMessage("Provide valid email"),
body("gender")
.optional()
.isString()
.withMessage("Gender should be string")
.isIn(["Male", "Female", "Other"])
.withMessage("Gender value is invalid"),
body("dateOfBirth")
.optional()
.isDate()
.withMessage("DOB should be valid date"),
body("phoneNumber")
.optional()
.isString()
.withMessage("phone number should be string")
.custom((value) => {
if (value.length !== 10) {
return Promise.reject("Phone number should be 10 digits");
} else {
return true;
}
}),
];
Explanation: express-validator provides chainable functions which we add as many validation rules as we want
In the code above we have used below validation middleware
body(): this will only validate req.body fields (if you want to validate param, query of request then param(), query() are also available) there is also check() available which will search for key in whole req object but only for req.body use body()
exists(): for required fields (makes field compulsory to include) there is also checkFalsy: true option available which also check if a value should not contain falsy value like "", null, undefined
withMessage(): custom message to display when validation fails
isString(): checks if value is string
isDate(): checks if it is valid date
optional(): value is optional
isIn(): check if input value contains one of value present in array.
isEmail(): checks for valid email id
custom(): write a custom validator for your own needs (you can also write async DB lookup validations here)
isLength({min: 2, max: 10}): check for min and max characters in value
Other common validator's
isNumeric(): checks if value is number
bail(): Stops running validations if any of the previous ones have failed.
isMobilePhone(): checks if input is valid phone number
💡 To explore other validator functions refer to validator.js
Now using userDataValidateChainableAPI, your /routes/user.router.js will be like this:
user.router.js
Error handing
To get the errors from user validation use validationResult() from the express-validator
In the user controller we will check the errors from the validation. if there are any then return all the errors
user.controller.js
const { validationResult } = require("express-validator");
const addUser = (req, res, next) => {
try {
const errors = validationResult(req);
// if there is error then return Error
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array(),
});
}
// save data to DB
User.create(req.body);
res.json({ success: true });
} catch (err) {
next(err);
}
};
module.exports = { addUser };
Error Object
When the POST - /api/user is hit then we will get errors (if we have not provided req.body as required by our schema)
Below is how the error object will look like
{
"success": false,
"errors": [
{
"value": "tet",
"msg": "Password should be at least 5 characters",
"param": "password",
"location": "body"
},
{
"value": "test.gmail",
"msg": "Provide valid email",
"param": "email",
"location": "body"
}
]
}
Schema based validation
The chain api for validation provided by express-validator is great but it can also get very hard to read if a single field has many validations. then a single validation middleware will have chain function hell
To solve this problem there is also schema-based validation in express-validator. this offers a clear approach where instead of chaining new function for new validation we have an object with key and values as validation schema at one place.
checkSchema() takes schema object as parameter and will be placed inside our user router middleware
let's create same validation in schema-based validation
user.validation.js
const userDataValidateSchemaBased = checkSchema({
userName: {
exists: {
errorMessage: "User name is required",
options: { checkFalsy: true },
},
isString: { errorMessage: "User name should be string" },
},
password: {
exists: { errorMessage: "Password is required" },
isString: { errorMessage: "password should be string" },
isLength: {
options: { min: 5 },
errorMessage: "Password should be at least 5 characters",
},
},
email: {
isEmail: { errorMessage: "Please provide valid email" },
},
gender: {
isString: { errorMessage: "Gender should be string" },
isIn: {
options: [["Male", "Female", "Other"]],
errorMessage: "Gender is invalid",
},
},
dateOfBirth: {
isDate: { errorMessage: "DOB should be string" },
},
phoneNumber: {
isString: { errorMessage: "phone number should be string" },
options: (value) => {
value.length === 10;
},
errorMessage: "Phone number should be 10 digits",
},
});
To use schema based object our user.router.js will look like this
checkSchema() is used for schema validation
user.router.js
Testing /api/user using Postman
run project using npm run start
Comments