Improve mongoose validation error handling
Asked Answered
A

4

10

I have the following schema with required validations:

var mongoose = require("mongoose");
var validator = require("validator");

var userSchema = new mongoose.Schema(
  {
    email: {
      type: String,
      required: [true, "Email is a required field"],
      trim: true,
      lowercase: true,
      unique: true,
      validate(value) {
        if (!validator.isEmail(value)) {
          throw new Error("Please enter a valid E-mail!");
        }
      },
    },
    password: {
      type: String,
      required: [true, "Password is a required field"],
      validate(value) {
        if (!validator.isLength(value, { min: 6, max: 1000 })) {
          throw Error("Length of the password should be between 6-1000");
        }

        if (value.toLowerCase().includes("password")) {
          throw Error(
            'The password should not contain the keyword "password"!'
          );
        }
      },
    },
  },
  {
    timestamps: true,
  }
);

var User = mongoose.model('User', userSchema);

I pass the email and password through a form by sending post request using the following route:

router.post("/user", async (req, res) => {
  try {
    var user = new User(req.body);
    await user.save();
    res.status(200).send(user);
  } catch (error) {
    res.status(400).send(error);
  }
});

module.exports = mongoose.model("User", user);

Whenever I enter a field against the validation rules, I get a very long error message, which is obvious. But now, I want to improve the error handling so that it gets easy to interpret for the users. Rather than redirecting to a generic error page, how can I redirect to the same signup page and display the flash messages near the incorrect fields telling about the error? And also on success, something similar should be done, like a green flash message on the top.

I am using ejs for my signup pages.

Andrus answered 6/4, 2020 at 8:46 Comment(0)
H
23

In the catch block, you can check if the error is a mongoose validation error, and dynamically create an error object like this:

router.post("/user", async (req, res) => {
  try {
    var user = new User(req.body);
    await user.save();
    res.status(200).send(user);
  } catch (error) {
    if (error.name === "ValidationError") {
      let errors = {};

      Object.keys(error.errors).forEach((key) => {
        errors[key] = error.errors[key].message;
      });

      return res.status(400).send(errors);
    }
    res.status(500).send("Something went wrong");
  }
});

When we send a request body like this:

{
   "email": "test",
   "password": "abc"
}

Response will be:

{
    "email": "Please enter a valid E-mail!",
    "password": "Length of the password should be between 6-1000"
}
Howlyn answered 6/4, 2020 at 9:11 Comment(1)
Thanks! it helped! Also, how can I display the errors like flash messages on the Sign Up page, and let the user fill the form again?Andrus
D
1

you can use validator like this instead of throwing an error :

password:{
    type:String,
    required:[true, "Password is a required field"],

validate: {
  validator: validator.isLength(value,{min:6,max:1000}),
  message: "Length of the password should be between 6-1000"
}
    }
Demesne answered 6/4, 2020 at 9:16 Comment(0)
R
0

you can send

res.status(400).send(error.message);

instead of :

res.status(400).send(error);

and you should make some changes in Schema also.

validate(value){
            if (!validator.isEmail(value)) {
                throw new Error("Please enter a valid E-mail!");
            }
        }

with

validate: [validator.isEmail, "Please enter a valid E-mail!" ]

and for password:

minlength: 6,
maxlength:1000,
validate:{
validator: function(el){
return el.toLowerCase() !== "password"
}
message: 'The password should not contain the keyword "password"!'
}
Relish answered 6/4, 2020 at 9:19 Comment(5)
and one more thing i look into your code you are directlly saving your req. body in user collection it may very big security flaw for user documents.Relish
I have actually added a pre-hook to hash the password using bcryptjs before the save() function. But not included here in the question, to keep it to the point.Andrus
thats good, but the purpose of mine writing this comment was: there may some keys in user schema that we might not want to save at the time of user create at there account for exaple isAdmin, isPrime user or even prfilepic but if some body hits the api at the time of requesting these key will also be save in user document at the time of creation. that uselly we don't want...Relish
What is the solution for that? It'd be really helpful.Andrus
replace: var user = new User(req.body); with const data={email: req. body. email, password: req. body. password, anyOther: req. body. other};var user = new User(data) ; //rest of the code. this data object will discard unwanted data passed by clent while creation there profile.. hope you got my pointRelish
M
0

If you are using mongoose then you can catch error easily using this method for signup route, by chaining if and else condition depending on your error methods

try {
    
} catch (error) {
    console.log(error);

        if (error.errors) {
            if (error.errors.password) {
                //if password is less than 6 characters
                return res.status(400).json(error.errors.password.message)
            }
            if (error.errors.email) {
               //if email is not in correct format. i'm using validator package
                return res.status(400).json(error.errors.email.message)
            }
        } else {
            return res.status(400).json(error.message)
        }
}

if found simpler one here..

try { } catch (error) {
    console.log(error);
    // checking validation
    if (error.name === "ValidationError") {
        
        const message = Object.values(error.errors).map(value => value.message);
        return res.status(400).json({
            error: message
        })
    }
    res.status(400).json(error.message)
}

}

Modality answered 15/11, 2022 at 8:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.