Mongoose virtuals are document properties that you can get and set but are not saved in MongoDB. These properties are computed whenever you access them.

Virtual properties are useful for formatting and combining fields, and de-composing a single value into multiple values before storing in the collection.

A common use-case for Mongoose's virtual property is the full name of a person that consists of first and last name.

Let us say that you have got the following Mongoose schema:

const mongoose = require('mongoose');
const { Schema } = mongoose;

// Define a schema
const userSchema = new Schema({
    name: {
        first: String,
        last: String
    },
    age: Number
});

// Define a model
const User = mongoose.model('User', userSchema);

// Create a document
const alex = new User({
    name: { first: 'Alex', last: 'Jones' },
    age: 32
});

Now you want to get the full name of alex, you can do this manually by concatenating the first and last name properties:

console.log(`${alex.name.first} ${alex.name.last}`);    // Alex Jones

Defining a virtual property

Instead of combining the first and last name every time, a better approach would be to define a virtual property called fullName. Mongoose provides two types of virtual methods: GET and SET.

GET Method

Virtual get methods are useful for formatting and combining document fields. You can also do any other complex processing before returning a value:

// Define setter virtual method
userSchema.virtual('fullName').get(function () {
    return `${this.name.first} ${this.name.last}`;
});

Now whenever you access the fullName property, Mongoose will invoke your virtual method and return the result:

console.log(alex.fullName);    // Alex Jones

SET Method

The setter virtual methods are useful to split strings into sub-strings and do other operations.

The following example demonstrates how you can split the name property into first and last name at any whitespace:

// Define setter virtual method
userSchema.virtual('fullName').set(function (name) {
    const tokens = name.split(' ');
    this.name.first = tokens[0];
    this.name.last = tokens[1];
});

Now, whenever you assign a value to the fullName property, it will be automatically split into first and last name:

const john = new User({
    fullName: 'John Doe',
    age: 32
});

console.log(john.name.first);  // John
console.log(john.name.last);   // Doe

Mongoose virtual setters are applied before any other validation. So the above example would still work even if the first and last properties were required.

Queries and field selection

Mongoose virtuals are just computed properties and are not available for queries and field selection. For example, you can not use the fullName property to select a user from the database:

// This query won't return any result
User.findOne({
    fullName: 'Alex Jones'
})
.then(alex => console.log(alex))
.catch(err => console.log(err));

Only non-virtual properties work for queries and field selections. Since virtuals are not stored in MongoDB, you can not use them in queries.

Including virtuals in JSON

By default, Mongoose ignores virtual properties when you convert a document into a JSON string by using either toJSON() or toObject():

// console.log(john.toJSON());
{
  name: { first: 'John', last: 'Doe' },
  _id: 5fd9b24943e7fa3e6ecf6a3f,
  age: 32
}

However, you can pass { virtuals: true } to the toJSON() method to include virtuals properties as well:

// console.log(john.toJSON({ virtuals: true }));
{
  name: { first: 'John', last: 'Doe' },
  _id: 5fd9b24943e7fa3e6ecf6a3f,
  age: 32,
  fullName: 'John Doe',
  id: '5fd9b24943e7fa3e6ecf6a3f'
}

Alternatively, you could also configure Mongoose to automatically include virtual properties for all documents:

mongoose.set('toJSON', { virtuals: true });

This is another reason why you should always use virtuals instead of instance methods in Mongoose if you want to include them in JSON.

Take a look at this article to learn more about Mongoose schemas.

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.