Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

express-validator returns validation errors twice

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?

like image 291
Question3r Avatar asked Sep 23 '19 19:09

Question3r


People also ask

Which is better express validator or Joi?

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.

What is validationResult in express validator?

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.

What is not () in express validator?

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.

What is bail in express validator?

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.


Video Answer


2 Answers

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,
  ];
}
like image 165
hoangdv Avatar answered Oct 12 '22 22:10

hoangdv


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 })
        })
    }
}
like image 36
Philipp Otto Avatar answered Oct 12 '22 23:10

Philipp Otto