Reputation: 16395
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
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
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
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
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
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
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
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
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
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