Mongoose Virtuals
Suppose you have a User
model. Every user has an email
, but you also want the email’s domain. For example, the domain portion of ‘test@gmail.com’ is ‘gmail.com’.
Below is one way to implement the domain
property using a virtual. You define virtuals on a schema using the Schema#virtual()
function.
The Schema#virtual()
function returns a . Unlike normal document properties, virtuals do not have any underlying value and Mongoose does not do any type coercion on virtuals. However, virtuals do have getters and setters, which make them ideal for computed properties, like the domain
example above.
Virtual Setters
const userSchema = mongoose.Schema({
lastName: String
});
// Create a virtual property `fullName` with a getter and setter.
userSchema.virtual('fullName').
get(function() { return `${this.firstName} ${this.lastName}`; }).
set(function(v) {
// `firstName` and `lastName`.
const firstName = v.substring(0, v.indexOf(' '));
const lastName = v.substring(v.indexOf(' ') + 1);
this.set({ firstName, lastName });
});
const User = mongoose.model('User', userSchema);
const doc = new User();
// Vanilla JavaScript assignment triggers the setter
doc.fullName; // 'Jean-Luc Picard'
doc.firstName; // 'Jean-Luc'
doc.lastName; // 'Picard'
By default, Mongoose does not include virtuals when you convert a document to JSON. For example, if you pass a document to , virtuals will not be included by default.
To include virtuals in res.json()
, you need to set the toJSON
schema option to { virtuals: true }
.
Virtuals in console.log()
By default, Mongoose does not include virtuals in output. To include virtuals in console.log()
, you need to set the to { virtuals: true }
, or use toObject()
before printing the object.
console.log(doc.toObject({ virtuals: true }));
If you use lean()
for performance, but still need virtuals, Mongoose has an officially supported mongoose-lean-virtuals
plugin that decorates lean documents with virtuals.
Limitations
Mongoose virtuals are not stored in MongoDB, which means you can’t query based on Mongoose virtuals.
// Will **not** find any results, because `domain` is not stored in
// MongoDB.
mongoose.set('debug', true)
const doc = await User.findOne({ domain: 'gmail.com' }, null, { strictQuery: false });
doc; // undefined
If you want to query by a computed property, you should set the property using a or pre save middleware.
- The
ref
option, which tells Mongoose which model to populate documents from. - The
localField
andforeignField
options. Mongoose will populate documents from the model inref
whoseforeignField
matches this document’slocalField
.