Validation
Before we get into the specifics of validation syntax, please keep the following rules in mind:
- Validation is defined in the SchemaType
- Validation is an internal piece of middleware
- Validation occurs when a document attempts to be saved, after defaults have been applied
- Validators are not run on undefined values. The only exception is the
required
validator. - Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
- Validation supports complete customization
Built in validators
Mongoose has several built in validators.
- All SchemaTypes have the built in required validator.
- Numbers have min and max validators.
- Strings have enum, match, maxlength and minlength validators.
Each of the validator links above provide more information about how to enable them as well as customize their associated error messages.
Custom validators
If the built-in validators aren't enough, validation can be completely tailored to suit your needs.
Custom validation is declared by passing a validation function.
You can find detailed instructions on how to do this in the
SchemaType#validate()
API docs.
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /d{3}-d{3}-d{4}/.test(v);
},
message: '{VALUE} is not a valid phone number!'
}
}
});
var User = mongoose.model('user', userSchema);
var u = new User();
u.phone = '555.0123';
// Prints "ValidationError: 555.0123 is not a valid phone number!"
console.log(u.validateSync().toString());
u.phone = '201-555-0123';
// Prints undefined - validation succeeded!
console.log(u.validateSync());
Validation errors
Errors returned after failed validation contain an errors
object
holding the actual ValidatorError
objects. Each
ValidatorError has kind
, path
,
value
, and message
properties.
var toySchema = new Schema({
color: String,
name: String
});
var Toy = mongoose.model('Toy', toySchema);
Toy.schema.path('color').validate(function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var toy = new Toy({ color: 'grease'});
toy.save(function (err) {
// err is our ValidationError object
// err.errors.color is a ValidatorError object
console.log(err.errors.color.message) // prints 'Validator "Invalid color" failed for path color with value `grease`'
console.log(String(err.errors.color)) // prints 'Validator "Invalid color" failed for path color with value `grease`'
console.log(err.errors.color.kind) // prints "Invalid color"
console.log(err.errors.color.path) // prints "color"
console.log(err.errors.color.value) // prints "grease"
console.log(err.name) // prints "ValidationError"
console.log(err.message) // prints "Validation failed"
});
After a validation error, the document will also have the same errors
property available:
toy.errors.color.message === err.errors.color.message
Synchronous Validation
Validation is asynchronous by default. Mongoose evaluates each individual
path in a separate process.nextTick()
call, which helps make sure
validation doesn't block the event loop if there are a lot of paths to
validate. However, mongoose's built-in validators are all synchronous, and
it's often more convenient to validate synchronously.
Mongoose documents also have a validateSync()
function, which returns
a ValidationError
if there was an error, or falsy if there was no error.
Note that validateSync()
only executes synchronous validators.
Custom asynchronous validators won't execute.
var toySchema = new Schema({
color: String,
name: String
});
var Toy = mongoose.model('Toy', toySchema);
Toy.schema.path('color').validate(function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var toy = new Toy({ color: 'grease'});
// `error` is a ValidationError analogous to the one from `validate()`
var error = toy.validateSync();
// prints 'Validator "Invalid color" failed for path color with value `grease`'
console.log(error.errors.color.message);
Update Validators
In the above examples, you learned about document validation. Mongoose also
supports validation for update()
and findOneAndUpdate()
operations.
In Mongoose 4.x, update validators are off by default - you need to specify
the runValidators
option.
var toySchema = new Schema({
color: String,
name: String
});
var Toy = mongoose.model('Toy', toySchema);
Toy.schema.path('color').validate(function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
Toy.update({}, { color: 'bacon' }, { runValidators: true }, function (err) {
console.log(err.errors.color.message); // prints 'Validator "Invalid color" failed for path color with value `bacon`'
});
There are a couple of key differences between update validators and
document validators. In the color validation function above, this
refers
to the document being validated when using document validation.
However, when running update validators, the document being updated
may not be in the server's memory, so by default the value of this
is
not defined. However, you can set the context
option to 'query' to make
this
refer to the underlying query.
Toy.schema.path('color').validate(function (value) {
this.schema; // refers to the query's schema if you set the `context` option
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var options = { runValidators: true, context: 'query' };
Toy.update({}, { color: 'bacon' }, options, function (err) {
console.log(err.errors.color.message); // prints 'Validator "Invalid color" failed for path color with value `bacon`'
});
The other key difference that update validators only run on the paths specified in the update. For instance, in the below example, because 'name' is not specified in the update operation, update validation will succeed.
var toySchema = new Schema({
color: String,
name: { type: String, required: true }
});
var Toy = mongoose.model('Toy', toySchema);
Toy.update({}, { color: 'blue' }, { runValidators: true }, function(err) {
// Operation succeeds despite the fact that 'name' is not specified
});
When using update validators, required
validators only fail when
you try to explicitly $unset
the key.
var unsetOp = { $unset: { name: 1 } };
Toy.update({}, unsetOp, { runValidators: true }, function(err) {
// Operation fails because 'name' is required
});
One final detail worth noting: update validators only run on $set
and $unset
operations. For instance, the below update will succeed,
regardless of the value of number
.
var testSchema = new Schema({
number: { type: Number, max: 0 },
});
var Test = mongoose.model('Test', testSchema);
Test.update({}, { $inc: { number: 1 } }, { runValidators: true }, function (err) {
// There will never be a validation error here
});
Next Up
Now that we've covered validation
, let's take a look at how you might handle advanced validation with Mongoose's middleware.