# Getting started with Mongoose virtuals

By Atta Ur Rehman Shah (https://attacomsian.com/about). Published Dec 16, 2020, updated Oct 3, 2022. Topics: Node.js, Mongoose, MongoDB.
Canonical URL: https://attacomsian.com/blog/mongoose-virtuals

> A quick article to learn about Mongoose virtuals and how to define and use them in Mongoose schema.

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

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

A simple use case for Mongoose's virtual property is the full name consisting of first and last names.

Let us say that you have got the following [Mongoose schema](https://attacomsian.com/blog/mongoose-schemas#defining-a-schema):

```javascript
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:

```javascript
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:

```javascript
// 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:

```javascript
console.log(alex.fullName)    // Alex Jones
```

### SET Method

The setter virtual methods are used 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:

```javascript
// 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 names:

```javascript
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:

```javascript
// 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](https://attacomsian.com/blog/json-parse-stringify#jsonstringify) using either `toJSON()` or `toObject()`:

```javascript
// 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:

```javascript
// 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:

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

This is another reason you should always use virtuals instead of [instance methods](https://attacomsian.com/blog/mongoose-schemas#instance-methods) in Mongoose if you want to include them in JSON.

Read [this article](https://attacomsian.com/blog/mongoose-schemas) to learn more about Mongoose schemas.
