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 middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
    • You can manually run validation using doc.validate(callback) or doc.validateSync()
    • 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 is customizable
    1. var schema = new Schema({
    2. name: {
    3. type: String,
    4. required: true
    5. }
    6. });
    7. var Cat = db.model('Cat', schema);
    8. // This cat has no name :(
    9. var cat = new Cat();
    10. cat.save(function(error) {
    11. assert.equal(error.errors['name'].message,
    12. 'Path `name` is required.');
    13. error = cat.validateSync();
    14. assert.equal(error.errors['name'].message,
    15. 'Path `name` is required.');
    16. });

    Built-in Validators

    Mongoose has several built-in validators.

    Each of the validator links above provide more information about how to enable them and customize their error messages.

    1. var breakfastSchema = new Schema({
    2. eggs: {
    3. type: Number,
    4. min: [6, 'Too few eggs'],
    5. max: 12
    6. },
    7. bacon: {
    8. type: Number,
    9. required: [true, 'Why no bacon?']
    10. },
    11. drink: {
    12. type: String,
    13. enum: ['Coffee', 'Tea'],
    14. required: function() {
    15. return this.bacon > 3;
    16. }
    17. }
    18. });
    19. var Breakfast = db.model('Breakfast', breakfastSchema);
    20. var badBreakfast = new Breakfast({
    21. eggs: 2,
    22. bacon: 0,
    23. drink: 'Milk'
    24. });
    25. var error = badBreakfast.validateSync();
    26. assert.equal(error.errors['eggs'].message,
    27. 'Too few eggs');
    28. assert.ok(!error.errors['bacon']);
    29. assert.equal(error.errors['drink'].message,
    30. '`Milk` is not a valid enum value for path `drink`.');
    31. badBreakfast.bacon = 5;
    32. badBreakfast.drink = null;
    33. error = badBreakfast.validateSync();
    34. assert.equal(error.errors['drink'].message, 'Path `drink` is required.');
    35. badBreakfast.bacon = null;
    36. error = badBreakfast.validateSync();
    37. assert.equal(error.errors['bacon'].message, 'Why no bacon?');

    The unique Option is Not a Validator

    A common gotcha for beginners is that the unique option for schemas is not a validator. It’s a convenient helper for building MongoDB unique indexes. See the FAQ for more information.

    1. var uniqueUsernameSchema = new Schema({
    2. username: {
    3. type: String,
    4. unique: true
    5. }
    6. });
    7. var U1 = db.model('U1', uniqueUsernameSchema);
    8. var U2 = db.model('U2', uniqueUsernameSchema);
    9. var dup = [{ username: 'Val' }, { username: 'Val' }];
    10. U1.create(dup, function(error) {
    11. // Will save successfully!
    12. });
    13. // Need to wait for the index to finish building before saving,
    14. // otherwise unique constraints may be violated.
    15. U2.on('index', function(error) {
    16. assert.ifError(error);
    17. U2.create(dup, function(error) {
    18. // Will error, but will *not* be a mongoose validation error, it will be
    19. // a duplicate key error.
    20. assert.ok(error);
    21. assert.ok(!error.errors);
    22. assert.ok(error.message.indexOf('duplicate key error') !== -1);
    23. });
    24. });
    25. // There's also a promise-based equivalent to the event emitter API.
    26. // The `init()` function is idempotent and returns a promise that
    27. // will resolve once indexes are done building;
    28. U2.init().then(function() {
    29. U2.create(dup, function(error) {
    30. // Will error, but will *not* be a mongoose validation error, it will be
    31. // a duplicate key error.
    32. assert.ok(error);
    33. assert.ok(!error.errors);
    34. assert.ok(error.message.indexOf('duplicate key error') !== -1);
    35. });
    36. });

    Custom Validators

    If the built-in validators aren’t enough, you can define custom validators 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.

    1. var userSchema = new Schema({
    2. phone: {
    3. type: String,
    4. validate: {
    5. validator: function(v) {
    6. return /\d{3}-\d{3}-\d{4}/.test(v);
    7. },
    8. message: '{VALUE} is not a valid phone number!'
    9. },
    10. required: [true, 'User phone number required']
    11. }
    12. });
    13. var User = db.model('user', userSchema);
    14. var user = new User();
    15. var error;
    16. user.phone = '555.0123';
    17. error = user.validateSync();
    18. assert.equal(error.errors['phone'].message,
    19. '555.0123 is not a valid phone number!');
    20. user.phone = '';
    21. error = user.validateSync();
    22. assert.equal(error.errors['phone'].message,
    23. 'User phone number required');
    24. user.phone = '201-555-0123';
    25. // Validation succeeds! Phone number is defined
    26. // and fits `DDD-DDD-DDDD`
    27. error = user.validateSync();
    28. assert.equal(error, null);

    Async Custom Validators

    Custom validators can also be asynchronous. If your validator function takes 2 arguments, mongoose will assume the 2nd argument is a callback.

    Even if you don’t want to use asynchronous validators, be careful, because mongoose 4 will assume that all functions that take 2 arguments are asynchronous, like the validator.isEmail function. This behavior is considered deprecated as of 4.9.0, and you can shut it off by specifying isAsync: false on your custom validator.

    1. var userSchema = new Schema({
    2. phone: {
    3. type: String,
    4. validate: {
    5. // `isAsync` is not strictly necessary in mongoose 4.x, but relying
    6. // on 2 argument validators being async is deprecated. Set the
    7. // `isAsync` option to `true` to make deprecation warnings go away.
    8. isAsync: true,
    9. validator: function(v, cb) {
    10. setTimeout(function() {
    11. var phoneRegex = /\d{3}-\d{3}-\d{4}/;
    12. var msg = v + ' is not a valid phone number!';
    13. // First argument is a boolean, whether validator succeeded
    14. // 2nd argument is an optional error message override
    15. cb(phoneRegex.test(v), msg);
    16. }, 5);
    17. },
    18. // Default error message, overridden by 2nd argument to `cb()` above
    19. message: 'Default error message'
    20. },
    21. required: [true, 'User phone number required']
    22. },
    23. name: {
    24. type: String,
    25. // You can also make a validator async by returning a promise. If you
    26. // return a promise, do **not** specify the `isAsync` option.
    27. validate: function(v) {
    28. return new Promise(function(resolve, reject) {
    29. setTimeout(function() {
    30. resolve(false);
    31. }, 5);
    32. });
    33. }
    34. }
    35. });
    36. var User = db.model('User', userSchema);
    37. var user = new User();
    38. var error;
    39. user.phone = '555.0123';
    40. user.name = 'test';
    41. user.validate(function(error) {
    42. assert.ok(error);
    43. assert.equal(error.errors['phone'].message,
    44. '555.0123 is not a valid phone number!');
    45. assert.equal(error.errors['name'].message,
    46. 'Validator failed for path `name` with value `test`');
    47. });

    Validation Errors

    Errors returned after failed validation contain an errors object whose values are ValidatorError objects. Each ValidatorError has kind, path, value, and message properties. A ValidatorError also may have a reason property. If an error was thrown in the validator, this property will contain the error that was thrown.

    1. var toySchema = new Schema({
    2. color: String,
    3. name: String
    4. });
    5. var validator = function(value) {
    6. return /red|white|gold/i.test(value);
    7. };
    8. toySchema.path('color').validate(validator,
    9. 'Color `{VALUE}` not valid', 'Invalid color');
    10. toySchema.path('name').validate(function(v) {
    11. if (v !== 'Turbo Man') {
    12. throw new Error('Need to get a Turbo Man for Christmas');
    13. }
    14. return true;
    15. }, 'Name `{VALUE}` is not valid');
    16. var Toy = db.model('Toy', toySchema);
    17. var toy = new Toy({ color: 'Green', name: 'Power Ranger' });
    18. toy.save(function (err) {
    19. // `err` is a ValidationError object
    20. // `err.errors.color` is a ValidatorError object
    21. assert.equal(err.errors.color.message, 'Color `Green` not valid');
    22. assert.equal(err.errors.color.kind, 'Invalid color');
    23. assert.equal(err.errors.color.path, 'color');
    24. assert.equal(err.errors.color.value, 'Green');
    25. assert.equal(err.errors.name.message, 'Name `Power Ranger` is not valid');
    26. assert.equal(err.errors.name.value, 'Power Ranger');
    27. assert.equal(err.errors.name.reason.message,
    28. 'Need to get a Turbo Man for Christmas');
    29. assert.equal(err.name, 'ValidationError');
    30. });

    Required Validators On Nested Objects

    Defining validators on nested objects in mongoose is tricky, because nested objects are not fully fledged paths.

    1. var personSchema = new Schema({
    2. name: {
    3. first: String,
    4. last: String
    5. }
    6. });
    7. assert.throws(function() {
    8. // This throws an error, because 'name' isn't a full fledged path
    9. personSchema.path('name').required(true);
    10. }, /Cannot.*'required'/);
    11. // To make a nested object required, use a single nested schema
    12. var nameSchema = new Schema({
    13. first: String,
    14. last: String
    15. });
    16. personSchema = new Schema({
    17. name: {
    18. type: nameSchema,
    19. required: true
    20. }
    21. });
    22. var Person = db.model('Person', personSchema);
    23. var person = new Person();
    24. var error = person.validateSync();
    25. assert.ok(error.errors['name']);

    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.

    To turn on update validators, set the runValidators option for update() or findOneAndUpdate(). Be careful: update validators are off by default because they have several caveats.

    1. var toySchema = new Schema({
    2. color: String,
    3. name: String
    4. });
    5. var Toy = db.model('Toys', toySchema);
    6. Toy.schema.path('color').validate(function (value) {
    7. return /blue|green|white|red|orange|periwinkle/i.test(value);
    8. }, 'Invalid color');
    9. var opts = { runValidators: true };
    10. Toy.update({}, { color: 'bacon' }, opts, function (err) {
    11. assert.equal(err.errors.color.message,
    12. 'Invalid color');
    13. });

    Update Validators and this

    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.

    1. var toySchema = new Schema({
    2. color: String,
    3. name: String
    4. });
    5. toySchema.path('color').validate(function(value) {
    6. // When running in `validate()` or `validateSync()`, the
    7. // validator can access the document using `this`.
    8. // Does **not** work with update validators.
    9. if (this.name.toLowerCase().indexOf('red') !== -1) {
    10. return value !== 'red';
    11. }
    12. return true;
    13. });
    14. var Toy = db.model('ActionFigure', toySchema);
    15. var toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
    16. var error = toy.validateSync();
    17. assert.ok(error.errors['color']);
    18. var update = { color: 'red', name: 'Red Power Ranger' };
    19. var opts = { runValidators: true };
    20. Toy.update({}, update, opts, function(error) {
    21. // The update validator throws an error:
    22. // "TypeError: Cannot read property 'toLowerCase' of undefined",
    23. // because `this` is **not** the document being updated when using
    24. // update validators
    25. assert.ok(error);
    26. });

    The context option

    The context option lets you set the value of this in update validators to the underlying query.

    1. toySchema.path('color').validate(function(value) {
    2. // When running update validators with the `context` option set to
    3. // 'query', `this` refers to the query object.
    4. if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
    5. return value === 'red';
    6. }
    7. return true;
    8. });
    9. var Toy = db.model('Figure', toySchema);
    10. var update = { color: 'blue', name: 'Red Power Ranger' };
    11. // Note the context option
    12. var opts = { runValidators: true, context: 'query' };
    13. Toy.update({}, update, opts, function(error) {
    14. assert.ok(error.errors['color']);
    15. });

    Update Validator Paths

    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.

    When using update validators, required validators only fail when you try to explicitly $unset the key.

    1. var kittenSchema = new Schema({
    2. name: { type: String, required: true },
    3. age: Number
    4. });
    5. var Kitten = db.model('Kitten', kittenSchema);
    6. var update = { color: 'blue' };
    7. var opts = { runValidators: true };
    8. Kitten.update({}, update, opts, function(err) {
    9. // Operation succeeds despite the fact that 'name' is not specified
    10. });
    11. var unset = { $unset: { name: 1 } };
    12. Kitten.update({}, unset, opts, function(err) {
    13. // Operation fails because 'name' is required
    14. assert.ok(err);
    15. assert.ok(err.errors['name']);
    16. });

    Update Validators Only Run On Specified Paths

    One final detail worth noting: update validators only run on the following update operators:

    • $set
    • $unset
    • $push (>= 4.8.0)
    • $addToSet (>= 4.8.0)
    • $pull (>= 4.12.0)
    • $pullAll (>= 4.12.0)

    For instance, the below update will succeed, regardless of the value of number, because update validators ignore $inc. Also, $push, $addToSet, $pull, and $pullAll validation does not run any validation on the array itself, only individual elements of the array.

    1. var testSchema = new Schema({
    2. number: { type: Number, max: 0 },
    3. arr: [{ message: { type: String, maxLength: 10 } }]
    4. });
    5. // Update validators won't check this, so you can still `$push` 2 elements
    6. // onto the array, so long as they don't have a `message` that's too long.
    7. testSchema.path('arr').validate(function(v) {
    8. return v.length < 2;
    9. });
    10. var Test = db.model('Test', testSchema);
    11. var update = { $inc: { number: 1 } };
    12. var opts = { runValidators: true };
    13. Test.update({}, update, opts, function(error) {
    14. // There will never be a validation error here
    15. update = { $push: [{ message: 'hello' }, { message: 'world' }] };
    16. Test.update({}, update, opts, function(error) {
    17. // This will never error either even though the array will have at
    18. // least 2 elements.
    19. });
    20. });

    On $push and $addToSet

    New in 4.8.0: update validators also run on $push and $addToSet

    1. var testSchema = new Schema({
    2. numbers: [{ type: Number, max: 0 }],
    3. docs: [{
    4. name: { type: String, required: true }
    5. }]
    6. });
    7. var Test = db.model('TestPush', testSchema);
    8. var update = {
    9. $push: {
    10. numbers: 1,
    11. docs: { name: null }
    12. }
    13. };
    14. var opts = { runValidators: true };
    15. Test.update({}, update, opts, function(error) {
    16. assert.ok(error.errors['numbers']);
    17. assert.ok(error.errors['docs']);
    18. });