Handling Mongoose validation errors – where and how?
Solution 1:
At this point it seems logical to buy in to how mongoose handles errors.
You would not want your models to handle error messages. The presentation layer (controllers?) should rely on the type
to decide on which is the best user-friendly message to display (i18n considered).
There's also the case where validation may happen by using a middleware. In this case, the error message that will surface up to your controller is whatever you pass to the next()
callback.
So, for the case of middleware, although not documented, in order to keep a consistent validation API across your models you should directly use Mongoose's Error constructors:
var mongoose = require('mongoose');
var ValidationError = mongoose.Error.ValidationError;
var ValidatorError = mongoose.Error.ValidatorError;
schema.pre('save', function (next) {
if (/someregex/i.test(this.email)) {
var error = new ValidationError(this);
error.errors.email = new ValidatorError('email', 'Email is not valid', 'notvalid', this.email);
return next(error);
}
next();
});
That way you are guaranteed a consistent validation error handling even if the validation error originates from a middleware.
To properly match error messages to types I'd create an enum which would act as a static map for all possible types:
// my controller.js
var ValidationErrors = {
REQUIRED: 'required',
NOTVALID: 'notvalid',
/* ... */
};
app.post('/register', function(req, res){
var user = new userModel.Model(req.body);
user.save(function(err){
if (err) {
var errMessage = '';
// go through all the errors...
for (var errName in err.errors) {
switch(err.errors[errName].type) {
case ValidationErrors.REQUIRED:
errMessage = i18n('Field is required');
break;
case ValidationErrors.NOTVALID:
errMessage = i18n('Field is not valid');
break;
}
}
res.send(errMessage);
}
});
});
Solution 2:
I know the validator plugins are probably helpful, but I think the mongoose validation stuff is more intimidating than it really is complicated. It definitely looks complicated from the outside but once you start tearing into it, it's not so bad.
If you check out the code below, you'll see an example of how a custom error message can be returned using built-in validators.
All you have to do is set a second parameter, with your own custom error message, when setting up your fields.
Checkout the required
and minlength
and maxlength
fields below to see how I've setup a custom error message, and then check out the methods below as to how the error object can be accessed or sent to the front end:
// Grab dependencies:
var mongoose = require('mongoose');
// Setup a schema:
var UserSchema = new mongoose.Schema (
{
username: {
type: String,
minlength: [2, 'Username must be at least 2 characters.'],
maxlength: [20, 'Username must be less than 20 characters.'],
required: [true, 'Your username cannot be blank.'],
trim: true,
unique: true,
dropDups: true,
}, // end username field
},
{
timestamps: true,
},
);
// Export the schema:
module.exports = mongoose.model('User', UserSchema);
The above sets up our fields to have custom error messages. But how do we access them or send them to our front end? We could have the following method setup in our server controller, whose response data is sent back to angular:
var myControllerMethods = {
register : function(req, res) {
// Create a user based on the schema we created:
User.create(req.body)
.then(function(newUser) {
console.log('New User Created!', newUser);
res.json(newUser);
})
.catch(function(err) {
if (err.name == 'ValidationError') {
console.error('Error Validating!', err);
res.status(422).json(err);
} else {
console.error(err);
res.status(500).json(err);
}
})
},
};
If you ran the code above, and any of our mongoose validators did not pass, the error (err
) object will be grabbed by the .catch()
in the promise. If you console log this error, you'll see in that object is our custom message, depending upon which error got flagged.
Note: The above example is just for adding custom validation messages to the already built-in validations that Mongoose possesses (like required
, minlength
, maxlength
and so forth).
If you want to create more advanced validations, such as validating fields against regex patterns or the like, then you'll have to create custom validator
functions.
See the "Custom Validators" section at this link for a great example of how to add a validator right onto your field: http://mongoosejs.com/docs/validation.html.
Note: You can also use "pre save hooks" and "instance methods", but this is beyond the scope of this question and the built in validators and "Custom Validators" (link aforementioned) are easier routes.
Hope this helps!
Solution 3:
From Mongoose: https://github.com/leepowellcouk/mongoose-validator
Error Messages Custom error messages are now back in 0.2.1 and can be set through the options object:
validate({message: "String should be between 3 and 50 characters"}, 'len', 3, 50)
How I implemented this:
var emailValidator = [validate({message: "Email Address should be between 5 and 64 characters"},'len', 5, 64), validate({message: "Email Address is not correct"},'isEmail')];
var XXXX = new Schema({
email : {type: String, required: true, validate: emailValidator} });
My front end deals with required, so I don't ever expect the mongoose "required" error to make it to the user, more of a back end safe guard.