I\'m trying to decide how I want to handle validation errors in Mongoose.
I have defined my own validation rules
you can also use joi here. It is actually very useful package for validating the schema in node app.
for eg:
const Joi = require("joi");
const validateUser = user => {
const Schema = {
email: Joi.string().email().required(),
name: Joi.string().min(3).max(20).required(),
password: Joi.string().min(8).max(25).required()
}
return Joi.validate(user, Schema);
}
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);
// my controller.js or whatever your route handler file is
// This Handles multiple Schema Level Validations in 1 line
// user object created from model
let user = new Users({
firstName: req.body.firstName,
lastName: req.body.lastName
...
});
// Since the Error response are in <Model Name>:<Schema Name>:<Error Message>, ...
var err = user.validateSync();
if (err && err.message) return res.send(err.message.split(':')[2].split(',')[0]);
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!
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.