47

I want to use mongoose custom validation to validate if endDate is greater than startDate. How can I access startDate value? When using this.startDate, it doesn't work; I get undefined.

var a = new Schema({
  startDate: Date,
  endDate: Date
});

var A = mongoose.model('A', a);

A.schema.path('endDate').validate(function (value) {
  return diff(this.startDate, value) >= 0;
}, 'End Date must be greater than Start Date');

diff is a function that compares two dates.

6 Answers 6

106

You can do that using Mongoose 'validate' middleware so that you have access to all fields:

ASchema.pre('validate', function(next) {
    if (this.startDate > this.endDate) {
        next(new Error('End Date must be greater than Start Date'));
    } else {
        next();
    }
});

Note that you must wrap your validation error message in a JavaScript Error object when calling next to report a validation failure. 

Sign up to request clarification or add additional context in comments.

8 Comments

I have in my controller in nodejs exports.create = function (req, res, next) { var a = new A(req.body); // I added your code here but it does'nt work newEvent.save(function(err) { if (err) { return res.json(400, err); } return res.json({message:'success'}); }); @JohnnyHK : Where am I supposed to add your code example Thnks
This is a much cleaner approach than the accepted answer, thanks @JohnnyHK
@AdrienG Go ahead and post a new question with the full details if you still need help.
Maybe it's my fault but I found another way to do it: I used instead this.invalidate('myField', 'my error message.', this.myField); so it's ok thanks :)
Be aware pre('validate',... middleware does not execute for findOneAndUpdate() github.com/Automattic/mongoose/issues/964
|
40

An an alternative to the accepted answer for the original question is:

var mongoose = require('mongoose'),
  Schema = mongoose.Schema;

// schema definition
var ASchema = new Schema({
  startDate: {
    type: Date,
    required: true
  },
  endDate: {
    type: Date,
    required: true,
    validate: [dateValidator, 'Start Date must be less than End Date']
  }
});

// function that validate the startDate and endDate
function dateValidator(value) {
  // `this` is the mongoose document
  return this.startDate <= value;
}

4 Comments

Great! And if you require one of two fields you can create the validation on a required field, but check what you need via this.
this option fails when use .findOneAndUpdate with runValidators options. this has wrong context so .startDate will not be available :(
Same problem when using findByIdAndUpdate with runValidators:true as Denis mentioned
@Denis For me this solution resolved the issue that you mentioned. Also, check out my comment under the linked answer. Hope this help!
32

I wanted to expand upon the solid answer from @JohnnyHK (thank you) by tapping into this.invalidate:

Schema.pre('validate', function (next) {
  if (this.startDate > this.endDate) {
    this.invalidate('startDate', 'Start date must be less than end date.', this.startDate);
  }

  next();
});

This keeps all of the validation errors inside of a mongoose.Error.ValidationError error. Helps to keep error handlers standardized. Hope this helps.

2 Comments

Just what I needed! Now I just need to figure out why mongoose isn't hitting the validate hook before saving...
Nix the above, this solution is perfect. It should be the accepted. Thanks @kognizant !!!
29

You could try nesting your date stamps in a parent object and then validate the parent. For example something like:

//create a simple object defining your dates
var dateStampSchema = {
  startDate: {type:Date},
  endDate: {type:Date}
};

//validation function
function checkDates(value) {
   return value.endDate < value.startDate; 
}

//now pass in the dateStampSchema object as the type for a schema field
var schema = new Schema({
   dateInfo: {type:dateStampSchema, validate:checkDates}
});

Comments

6

Using 'this' within the validator works for me - in this case when checking the uniqueness of email address I need to access the id of the current object so that I can exclude it from the count:

var userSchema = new mongoose.Schema({
  id: String,
  name: { type: String, required: true},
  email: {
    type: String,
    index: {
      unique: true, dropDups: true
    },
    validate: [
      { validator: validator.isEmail, msg: 'invalid email address'},
      { validator: isEmailUnique, msg: 'Email already exists'}
    ]},
  facebookId: String,
  googleId: String,
  admin: Boolean
});

function isEmailUnique(value, done) {
  if (value) {
    mongoose.models['users'].count({ _id: {'$ne': this._id }, email: value }, function (err, count) {
      if (err) {
        return done(err);
      }
      // If `count` is greater than zero, "invalidate"
      done(!count);
    });
  }
}

Comments

1

This is the solution I used (thanks to @shakinfree for the hint) :

var mongoose = require('mongoose'),
Schema = mongoose.Schema;

// schema definition
var ASchema = new Schema({
  dateSchema : {
                type:{
                    startDate:{type:Date, required: true}, 
                    endDate:{type:Date, required: true}
                }, 
                required: true, 
                validate: [dateValidator, 'Start Date must be less than End Date']
            }
});

// function that validate the startDate and endDate
function dateValidator (value) {
    return value.startDate <= value.endDate;
}

module.exports = mongoose.model('A', ASchema);

1 Comment

Be carefully with this aproach because there is bug in 4.01 github.com/Automattic/mongoose/issues/2814

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.