zemirco
zemirco

Reputation: 16395

Best way to check for mongoose validation error

I've got two validation functions for my usermodel

User.schema.path('email').validate(function(value, respond) {
  User.findOne({email: value}, function(err, user) {
    if(err) throw err;
    if(user) return respond(false);
    respond(true);
  });
}, 'EMAIL_EXISTS');

and the same for username

User.schema.path('username').validate(function(value, respond) {
  User.findOne({username: value}, function(err, user) {
    if(err) throw err;
    if(user) return respond(false);
    respond(true);
  });
}, 'USERNAME_TAKEN');

They return errors in the following format

{ message: 'Validation failed',
  name: 'ValidationError',
  errors: 
    { username: 
      { message: 'Validator "USERNAME_TAKEN" failed for path username',
        name: 'ValidatorError',
        path: 'username',
        type: 'USERNAME_TAKEN' } } }

The error for the email path is similar. Is there a smarter way to check for those errors than the following?

if (err && err.errors && err.errors.username) { ... }

This is kind of ugly.

Upvotes: 36

Views: 63610

Answers (9)

NandaSagar
NandaSagar

Reputation: 11

NOTE: violating the constraint returns an E11000 error from MongoDB when saving, not a Mongoose validation error.

try {
  question.options.push(req.body.optiontext);
  await question.save();
  res.json(question);
} catch (err) {
  res.json({ message: "cannot add more than 4 options to a polling question" });
}

Upvotes: 0

Danial Shabbir
Danial Shabbir

Reputation: 650

Approach #1

My Best Guess for this approach is to use generalized validator with promise based returns, hope it may help down the road

function validateDoc(model,fieldName) {
  return new Promise((resolve, reject) => {
    model.validate(err => {
      if (err) {
        return reject(err.errors[fieldName]);
      } else {
        return resolve(model);
      }
    });
  })
}

// Access error message in catch() or get the validated doc in then()

validateDoc(model,fieldName)
           .then((model) => console.log(model))
           .catch((message) => console.warn(message))

Approach #2

const userSchema = new Schema({
  name: {
    type:String,
    required:[true,'Name is required.'],
    validate:{
      validator:(name) => { name.length <= 2 }
      message:'Name must be longer than two characters'
    }
  }
})
const User = mongoose.model('user',userSchema)

Use the validate:Object inside schema with validator:function and message:String

Later when u will try to save the record it will thow the validation error object in the catch function

For Example

const newUser = new User({ name: undefined });

newUser.save().catch( ( { errors } ) => { 

let errorBag = [];

Object.keys(errors).forEach((fieldName) => {

errorBag.push({ [fieldName]:errors[fieldName].message })

});

 // All the errors with the **fieldName** and  **errorMessages** 

console.log(errorBag);  

   })

Upvotes: 0

Sergi Ram&#243;n
Sergi Ram&#243;n

Reputation: 1744

Technically you must check first the error name because not all errors are handled the same way. Then, based on the error name you must check for particular properties, as the errors property that comes with a ValidationError.

Also you put the field name in the error type and this is redundant, it's better to use the same error type because in the error checking procedure you will get the field name also.

So your code can be something like:

User.schema.path('email').validate(function(value, respond) {
  User.findOne({email: value}, function(err, user) {
    if(err) throw err;
    if(user) return respond(false);
    respond(true);
  });
}, 'exists');

User.schema.path('username').validate(function(value, respond) {
  User.findOne({username: value}, function(err, user) {
    if(err) throw err;
    if(user) return respond(false);
    respond(true);
  });
}, 'exists');

And then, the error checking procedure:

if (err) {
  switch (err) {
    case err instanceof mongoose.Error.ValidationError:
      for (field in err.errors) {
        switch (err.errors[field].type) {
          case 'exists':
            ...
            break;
          case 'invalid':
            ...
            break;
          ...
        }
      }
      break;
    default:
      ...
  }
}

If you want to shorten this, you have various options. If you only have one type of validation you can do it like this:

if (err) {
  if (err instanceof mongoose.Error.ValidationError) {
    for (field in err.errors) {
      ...
    }
  } else {
    // A general error (db, crypto, etc…)
    ...
  }
}

The minimal expression of the error check procedure would be similar to what you've wrote in your post:

if (err) {
  for (field in err.errors) {
    ...
  }
}

This will work because if errors is not defined it will just ignore the for. But you're ignoring all other error types here.

I also think that these error layouts are a bit messing, but don't expect for this to change in a near future.

Upvotes: 36

Ankur Loriya
Ankur Loriya

Reputation: 3534

Here my unique way to handle mongoose validation error

The code is still in progress once it ready I will update it or you can contribute to extends my code.

let message = "";
let title = "Validation Error";
let code = 400;
let requiredFields = [];
for (let field in err.errors) {
    let subMsg = ""
    if (err.errors[field].kind === "required") {
        requiredFields.push(field)
    }
}
if (requiredFields.length > 0) {
    message = "Following fields are required: " + requiredFields.join(", ");
} else {
    message = "Unknown";
}
res.status(code).json({
    status: code,
    message: message,
    title: title
});

Upvotes: 0

Kunok
Kunok

Reputation: 8759

By reading all these answers, I consider it's the best to create utility function and reuse it as such:

This is the function that handles ValidationError by sending desired response to the client with validation messages, and optionally uses console.log to show messages in console.

function handleValidationError(err, res, consoleLog = false){
  const messages = []
  for (let field in err.errors) {
    messages.push(err.errors[field].message)
    consoleLog && console.log(err.errors[field].message)
  }
  res.status(422).json({ messages })
}

Then in controller where we want to handle error, we check if err.name is ValidationError, and if so, we use utility function from above.

user.save((err) => {
  if (err) {
    if (err.name === 'ValidationError') return handleValidationError(err, res) // here
    return res.status(500).json({ message: 'Error while creating new user' })
  }
  return res.status(201).json({ message: 'User created' })
})

Then client would get validation erros in response as such:

curl\
-H 'Content-Type: application/json'\
-d '{"email": "foo", "password": "barbaz"}'\
http://localhost:3000/user/new

Output:

{"messages":["Email validation failure"]}

Upvotes: 3

steampowered
steampowered

Reputation: 12062

I use AngularJS, so ngRepeat displays my validation errors on the web form. All I need to do is return an array of error messages.

Sometimes Mongoose will throw an error which is NOT a validation error, and in this case the err.errors object will not be present. I log the execution error. I still use the same spot on the web form to display the execution error to the user.

var makeMongooseErrorMsgArray = function(err){
    var msgArray = [];
    if (err.errors) { // validation errors
        $.each(err.errors, function (key, val) {
            msgArray.push(val.message);
        });
    } else if (err.message){ // should be execution error without err.errors
        errLogr.log(err); // log execution errors
        msgArray.push(err.message);
    } else {
        msgArray.push('Unknown error');
    }
    return msgArray;
}

Upvotes: 3

Aamir Afridi
Aamir Afridi

Reputation: 6411

I found this helpful which displays all errors in an array.

For example I submitted a form with short password and invalid email.

if (err && err.name === 'ValidationError') {
   err.toString().replace('ValidationError: ', '').split(',')
}

Which results in this

[ 'Please provide a valid email address',
'The password should be at least 6 characters long' ]

If you have a comma , in your error messages than try without .split(',')

No need for for loops. Make sure you have validation error messages in your schema. For the above example I have

const validateEmail = email => {
  const re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
  return re.test(email);
};

const Schema = mongoose.Schema;
const userSchema = new Schema({
   ...
   email: {
      type: String,
      trim: true,
      required: 'Email address is required',
      validate: [validateEmail, 'Please provide a valid email address'],
   },
   password: { type: String, set: encryptPassword, maxlength: [6, 'The password should be at least {MAXLENGTH} characters long'] },
   ...
});

Upvotes: 2

Sudhanshu Gaur
Sudhanshu Gaur

Reputation: 7664

Just write the following code and enjoy.

if (err) {
    console.log('Error Inserting New Data');
    if (err.name == 'ValidationError') {
        for (field in err.errors) {
            console.log(err.errors[field].message); 
        }
    }
}

Upvotes: 13

jviotti
jviotti

Reputation: 18929

Why don't you use the validation method as described in the API?

objectToSave.validate(function(err) {          
  if (err) {
    // handle error
  }     
  else {
    // validation passed
  }
});

Upvotes: 0

Related Questions