Mongoose Validation and Virtual Fields

Mongoose, MongoDB ORM for node.js, allows you to create virtual fields. However, virtual fields cannot be validated using the standard method of field validation. I will first explain how to add validation to normal, non-virtual fields, and then describe a method for handling virtual fields.

Suppose we have a model called User. Normally, we have a schema definition for the model.

var mongo = require('mongoose'),
    mongoTypes = require('mongoose-types'),
    var db = mongo.connect('mongodb://localhost/test'),
    Schema = mongo.Schema,
    UserSchema,
    User;

mongoTypes.loadTypes(mongo, 'email');

// Validator function
function required(val) { return val && val.length; }

UserSchema = new Schema({
  email: {
    type: mongo.SchemaTypes.Email,
    validate: [required, 'Email is required'],
    index: { unique: true }
  },
  hexdigest: {
    type: String,
    validate: [required, 'Password is required'],
    match: /[A-Za-z0-9]{12}\$[0-9a-f]{32}/
  },
  active: {
    type: Boolean,
    'default': false
  },
  createdAt: {
    type: Date,
    'default': Date.now
  }
});

mongo.model('User', UserSchema);
User = mongo.model('User');

You can see that there is no password field. It’s not a very clever idea to store a clear-text password with your user credentials data. Instead we create a hexdigest and store in the hexdigest field. (I won’t go into details of how you do that.)

Now, we still need to store the password temporarily so we add a virtual field.

....
'default': false
  },
  createdAt: {
    type: Date,
    'default': Date.now
  }
});

UserSchema.method('setPassword', function(cleartext) {
    var hex;
    // calculate the hexdigest and
    this.hexdigest = hex;
});

UserSchema.virtual('password').set(function(password){
  this.setPassword(password);
  this._password = password;
}).get(function(){
  return this._password;
});

As you can see, we are storing the clear-text password in _password field, and setting the password using the setPassword method.

Now, the trick here is to set the hexdigest field to null or some other falsy value (empty string will do), so that the required validator on hexdigest field fails if clear-text password is empty. So, if we set the virtual field to an empty value, we now know that validation for the real hexdigest field will fail, and we rely on this to sort of validate our virtual field.

In a controller callback, you might do something like:

var user = User(req.body.user);
user.save(function(err){
  if (err) {
    if (err.errors && err.errors.hasOwnProperty('hexdigest')) {
        res.send('Password is blank!');
    }
  } else {
    // It works!
  }
});

Still, there’s no way to validate purely virtual fields, that do not link to any real field. For this, you will have to validate it in pre-save callback, and throw an error yourself.