API with NestJS #45. Virtual properties with MongoDB and Mongoose

JavaScript MongoDB NestJS TypeScript

This entry is part 45 of 158 in the API with NestJS

In this series, we’ve used Mongoose to define properties in our schemas and work with models for documents. We’ve also defined various relations between collections. With Mongoose, we can also take advantage of virtual properties that are not stored in MongoDB. To understand them, we first grasp the concept of getters and setters.

You can find the code from this article in this repository.

Getters and setters with Mongoose

We can execute custom logic when we get and set properties in a document with getters and setters.


By using getters, we can modify the data of a document when we retrieve it. Let’s create an example when the user has a credit card number that we want to obfuscate when responding to the API request.


When we return the documents from our API, NestJS stringifies our data. When that happens, the method is called on our Mongoose models. Therefore, if we want our getters to be considered, we need to add to our configuration explicitly.

Documents also have the toObject method and we can customize it in a similar way.

We also use in our . For more details, check out API with NestJS #44. Implementing relationships with MongoDB

In our code above, we obfuscate the credit card number every time we return the user’s document from our API.

Mongoose assignes our schemas a virtual getter for the field. It now appears in the response because we’ve turned on getters through . More on virtuals later.

There are times where we want to access the original, non-modified property. To do that, we can use the Document.prototype.get() function.


With setters, we can modify the data before saving it in the database.


Thanks to doing the above, we now remove whitespace from both ends of the string.

While setters are a valid technique, you might prefer to put this logic in the service for increased readability. However, even if that’s the case, setters are worth knowing.

Virtual properties

A virtual is a property that we can get and set, but it is not stored inside the database. Let’s define a simple example of a use case.


The above approach is flawed, unfortunately. If we persist the property into MongoDB, we duplicate the information because we already have the and . A more appropriate approach would be to create the on the fly based on other properties.


We can achieve the above with the virtual property. So, let’s create it along with a getter.


Please notice that we don’t use the decorator on the property. Instead, we call the function at the bottom of the file.

Thanks to adding , our virtual properties are visible when converting a document to JSON. Even though we can see in the above response, it isn’t saved to the database.


With virtual, we can also create setters. We can use them, for example, to set multiple properties at once.


Above, we set the and properties based on the .

Populating virtual properties

A handy feature of virtual properties is using them to populate documents from another collection.

We learn the basics of the populate feature in API with NestJS #44. Implementing relationships with MongoDB

In an example in the previous article, we create a schema for a post, using it to store the reference to the author.


Therefore, when we fetch the User document, we don’t have information about any posts. We can use virtual properties to tackle this issue.


The last step is to call the function along with the document of the user. While we’re at it, we can also populate the nested property.



In this article, we’ve learned what virtual properties are how they can be useful. We’ve used them both to add simple properties and populate documents from other collections. To better grasp the concept of virtual, we’ve also investigated getters and setters. All of the above can surely come in handy when using Mongoose to define MongoDB schemas.

Series Navigation<< API with NestJS #44. Implementing relationships with MongoDBAPI with NestJS #46. Managing transactions with MongoDB and Mongoose >>
Notify of
Newest Most Voted
Inline Feedbacks
View all comments
2 years ago

Awesome features. Thanks for the articles. I was always wondering how can I setup 2 way relationship. With virtual, I will only need to manage one side.

2 years ago

Thank you for sharing. I can’t wait to see the next article

Mahmudul Hasan
2 years ago

I’m getting this error when using serializer interceptor

RangeError: Maximum call stack size exceeded
  at ctor.<computed> [as default] (D:\Workspaces\Fiverr\muhammedgeyik\mega-radio-api-nestjs\node_modules\mongoose\lib\statemachine.js:57:12)    
  at $__applyDefaults (D:\Workspaces\Fiverr\muhammedgeyik\mega-radio-api-nestjs\node_modules\mongoose\lib\document.js:523:40)

1 year ago
Reply to  Mahmudul Hasan

you must custom your ClassSerializeInterceptor like this:

Last edited 1 year ago by Twelve