Mongoose gives us a set of useful built-in validation rules such: Required validator checks if a property is not empty. Numbers have min and max validators. Strings have enum , match , minlength , and maxlength validators.
MongooseError : general Mongoose error. CastError : Mongoose could not convert a value to the type defined in the schema path. May be in a ValidationError class' errors property.
Mongoose has several built-in validators. All SchemaTypes have the built-in required validator. The required validator uses the SchemaType's checkRequired() function to determine if the value satisfies the required validator. Numbers have min and max validators.
Validation is an important part of the mongoose schema. Along with built-in validators, mongoose also provides the option of creating custom validations. Creating custom validations is also very simple. Custom validations can be used where built-in validators do not fulfill the requirements.
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);
}
});
});
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!
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.
The question you need to ask yourself is who is responsible for causing the error in the first place?
If this happens in your system, that you are in control over, just let the errors hit you like you normally would, and weed the bugs out as you go along, but I suspect you are doing an application which is facing real world users and you want to sanitize their inputs.
I would recommend that client side you check that the input is correct before you send it to your server, and you show nice helper messages like "Your username must be between x and y characters".
Then on the server side you expect that in 99% of the cases the input will come directly from your sanitizing client, and so you still validate it using the techniques you already suggested, but if there is an error you simply return a general error message to the user interface - since you trust that your user interface would have shown helper messages so the validation error must be caused by a bug or a hacking attempt.
Remember to log all server side validation errors as they could be severe bugs or someone looking for exploits.
Warning: As of Mongoose 4.1.3 the signature for function ValidatorError has completely changed and the information below is no more applicable:
As of Mongoose 3.8.12 the signature for function ValidatorError is:
function ValidatorError (path, msg, type, val)
Where type can be either "notvalid" or "required"
For example if your "email" field validation raises a validation error, you can simply do:
var error = new ValidationError(this);
error.errors.email =
new ValidatorError('email', "Your err message.", 'notvalid', this.email);
As of mongoose 4.5.0 Document#invalidate returns a ValidationError. See this https://github.com/Automattic/mongoose/issues/3964
Also, when trying to invalidate on a findOneAndUpdate query hook, you can do:
// pass null because there is no document instance
let err = new ValidationError(null)
err.errors[path] = new ValidatorError({
path: 'postalCode',
message: 'postalCode not supported on zones',
type: 'notvalid',
value,
})
throw(err)
See the hmv package, which helps you customize the mongoose error message templates, including unique index errors :
template : {PATH_NAME} must be at least {MIN_LENGTH} characters long
schema : { fullname : { type : String, min : 3, $name : 'Full name' } }
message : Full name must be at least 3 characters long
template : {PATH_NAME} {VALUE} have been used, please choose another
schema : { username : { type : String, unique : true } }
message : username MrBean have been used, please choose another
And specifically supports localization, ex in Vietnamese :
template : {PATH_NAME} dài ít nhất {MIN_LENGTH} kí tự
schema : { fullname : { type : String, min : 3, $name : 'tên tài khoản' } }
message : Tên tài khoản dài ít nhất 5 kí tự
Good point is that you only need to customize the message template once, instead of customizing every field of every schema in the previous approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With