Custom Casting
+Custom Casting
Working With Dates
+Working With Dates
How to Use findOneAndUpdate()
in Mongoose
+How to Use findOneAndUpdate()
in Mongoose
Getters/Setters in Mongoose
+Getters/Setters in Mongoose
Faster Mongoose Queries With Lean
+Faster Mongoose Queries With Lean
Query Casting
+Query Casting
Virtuals
+ + + + +In Mongoose, a virtual is a property that is not stored in MongoDB. +Virtuals are typically used for computed properties on documents.
+-
+
- Your First Virtual +
- Virtual Setters +
- Virtuals in JSON +
- Virtuals with Lean +
- Limitations +
- Populate +
- Further Reading +
Your First Virtual
+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.
const userSchema = mongoose.Schema({
+ email: String
+});
+// Create a virtual property `domain` that's computed from `email`.
+userSchema.virtual('domain').get(function() {
+ return this.email.slice(this.email.indexOf('@') + 1);
+});
+const User = mongoose.model('User', userSchema);
+
+let doc = await User.create({ email: 'test@gmail.com' });
+// `domain` is now a property on User documents.
+doc.domain; // 'gmail.com'
+The Schema#virtual()
function returns a VirtualType
object. 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
+You can also use virtuals to set multiple properties at once as an
+alternative to custom setters on normal properties. For example, suppose
+you have two string properties: firstName
and lastName
. You can
+create a virtual property fullName
that lets you set both of
+these properties at once. The key detail is that, in virtual getters and
+setters, this
refers to the document the virtual is attached to.
const userSchema = mongoose.Schema({
+ firstName: String,
+ 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) {
+ // `v` is the value being set, so use the value to set
+ // `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.fullName; // 'Jean-Luc Picard'
+doc.firstName; // 'Jean-Luc'
+doc.lastName; // 'Picard'
+Virtuals in JSON
+By default, Mongoose does not include virtuals when you convert a document to
+JSON. For example, if you pass a document to Express' res.json()
function,
+virtuals will not be included by default.
To include virtuals in res.json()
, you need to set the
+toJSON
schema option to { virtuals: true }
.
const opts = { toJSON: { virtuals: true } };
+const userSchema = mongoose.Schema({
+ _id: Number,
+ email: String
+}, opts);
+// Create a virtual property `domain` that's computed from `email`.
+userSchema.virtual('domain').get(function() {
+ return this.email.slice(this.email.indexOf('@') + 1);
+});
+const User = mongoose.model('User', userSchema);
+
+const doc = new User({ _id: 1, email: 'test@gmail.com' });
+
+doc.toJSON().domain; // 'gmail.com'
+// {"_id":1,"email":"test@gmail.com","domain":"gmail.com","id":"1"}
+JSON.stringify(doc);
+
+// To skip applying virtuals, pass `virtuals: false` to `toJSON()`
+doc.toJSON({ virtuals: false }).domain; // undefined
+Virtuals with Lean
+Virtuals are properties on Mongoose documents. If you use the
+lean option, that means your queries return POJOs
+rather than full Mongoose documents. That means no virtuals if you use
+lean()
.
const fullDoc = await User.findOne();
+fullDoc.domain; // 'gmail.com'
+
+const leanDoc = await User.findOne().lean();
+leanDoc.domain; // undefined
+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.
+const doc = await User.findOne({ domain: 'gmail.com' });
+doc; // undefined
+If you want to query by a computed property, you should set the property using +a custom setter or pre save middleware.
+Populate
+Mongoose also supports populating virtuals. A populated +virtual contains documents from another collection. To define a populated +virtual, you need to specify:
+-
+
- 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
.
+
const userSchema = mongoose.Schema({ _id: Number, email: String });
+const blogPostSchema = mongoose.Schema({
+ title: String,
+ authorId: Number
+});
+// When you `populate()` the `author` virtual, Mongoose will find the
+// first document in the User model whose `_id` matches this document's
+// `authorId` property.
+blogPostSchema.virtual('author', {
+ ref: 'User',
+ localField: 'authorId',
+ foreignField: '_id',
+ justOne: true
+});
+const User = mongoose.model('User', userSchema);
+const BlogPost = mongoose.model('BlogPost', blogPostSchema);
+
+await BlogPost.create({ title: 'Introduction to Mongoose', authorId: 1 });
+await User.create({ _id: 1, email: 'test@gmail.com' });
+
+const doc = await BlogPost.findOne().populate('author');
+doc.author.email; // 'test@gmail.com'
+