I want to validate the request object using Express-Validator. Let's assume I have two routes, a GET /users/:id (fetchUserById) and POST /users (createUser) route
this.router = express.Router();
this.router.route('/').post(this.userRequestValidator.createUser, this.userController.createUser);
this.router.route('/:id').get(this.userRequestValidator.fetchUserById, this.userController.fetchUserById);
As you can see I call the validation middleware right before calling the controller logic. First I created a base validator dealing with the validation errors and returning a HTTP 400 if something failed.
export abstract class RequestValidator {
protected validate = async (request: Request, response: Response, next: NextFunction): Promise<void> => {
const errors: Result<ValidationError> = validationResult(request);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
} else {
next();
}
};
}
My validator functions userRequestValidator.createUser and userRequestValidator.fetchUserById just have to extend the RequestValidator and implement the validations
export class UserRequestValidator extends RequestValidator {
public createUser = [
body('username')
.isString()
.exists(),
body('password')
.isString()
.exists(),
this.validate,
];
public fetchUserById = [
param('id')
.isString()
.isUUID()
.exists(),
this.validate,
];
}
When I call GET localhost:3000/users/abc
I get this response
{
"errors": [
{
"value": "abc",
"msg": "Invalid value",
"param": "id",
"location": "params"
}
]
}
This is the response I am expecting. But when I call POST localhost:3000/users
with an empty body I get this response
{
"errors": [
{
"msg": "Invalid value",
"param": "username",
"location": "body"
},
{
"msg": "Invalid value",
"param": "username",
"location": "body"
},
{
"msg": "Invalid value",
"param": "password",
"location": "body"
},
{
"msg": "Invalid value",
"param": "password",
"location": "body"
}
]
}
Does someone know how I can fix this behaviour or what's wrong with my setup?
Joi can be used for creating schemas (just like we use mongoose for creating NoSQL schemas) and you can use it with plain Javascript objects. It's like a plug n play library and is easy to use. On the other hand, express-validator uses validator. js to validate expressjs routes, and it's mainly built for express.
validationResult(req) Extracts the validation errors from a request and makes them available in a Result object. Each error returned by . array() and . mapped() methods has the following format by default: { "msg": "The error message", "param": "param.
notEmpty() adds a validator to check if a value is not empty; that is, a string with a length of 1 or bigger. https://express-validator.github.io/docs/validation-chain-api.html#notempty. Follow this answer to receive notifications.
According to the express-validator documentation: . bail() is useful to prevent a custom validator that touches a database or external API from running when you know it will fail. Can be used multiple times IN THE SAME validation chain if needed.
I don't know why when req.body
is a empty object - {}
, the validator will run through all node of validation chain. You can check again, add each message for each condition, like as follow:
class UserRequestValidator extends RequestValidator {
public createUser = [
body('username')
.isString().withMessage('username must be a string') // you can see both error messages in the response
.exists().withMessage('username must be exist'),
body('password') // the same for this field
.isString()
.exists(),
this.validate,
];
public fetchUserById = [
param('id') // because id is exist in `req.params`, then only one test has been executed.
.isString().withMessage('id must be a string')
.isUUID()
.exists(),
this.validate,
];
}
I found a solution for your case in https://github.com/express-validator/express-validator/issues/638 , stop chain in the first error with .bail()
function.
Then your validator class will be like:
class UserRequestValidator extends RequestValidator {
public createUser = [
body('username')
// always check exists() first
.exists().withMessage('username must be exist').bail()
.isString().withMessage('username must be a string').bail(),
body('password')
.exists().bail()
.isString().bail(),
this.validate,
];
public fetchUserById = [
param('id')
.isString()
.isUUID()
.exists(),
this.validate,
];
}
You can also set onlyFirstError
to true
when retrieving the error array.
From the documentation:
If the option onlyFirstError is set to true, then only the first error for each field will be included
Example usage:
function validateRequestParams (req, res, next) {
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
} else {
return res.status(400).json({
bodyValidationErrors: errors.array({ onlyFirstError: true })
})
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With