TypeScript Express tutorial #13. Using Mongoose virtuals to populate documents

Express JavaScript TypeScript

This entry is part 13 of 15 in the TypeScript Express tutorial

Throughout this series, we’ve created some schemas and models for documents. We’ve also established ways to define relationships between them. We sometimes find ourselves needing many different properties in our documents. They sometimes overlap each other, and we might want to avoid duplicating the data in the database. Also, we might want to avoid two-way referencing that we discussed in the fifth part of this series. The solution to all of the above issues might be virtuals. To understand them, we also inspect getters and setters first.

You can find all of the code from this series in this repository. Feel free to give it a star and share it.

Getters and Setters in Mongoose

The first concept to wrap our heads around is the idea of getters and setters. They allow us to execute custom logic whenever we are getting and setting properties in a document.

Getters

With getters, we can modify the data of a document. Let’s assume that the user has the credit card number that we don’t want to fully present.

In the above example, every time the User document is pulled from the database, we obfuscate the credit card number to protect it.

To make our changes reflect in the response, we need to pass additional options to the   constructor. When we respond with the data of a user, Express calls the  function internally. If we want the getters to apply, we need to set   as above.

Documents also have the toObject method that we can also customize similarly.

A useful use-case for the above might be to make sure that the password of the user never gets sent back in response.

By creating such a getter, we avoid leaking sensitive data. We just need to remember that if we need to access the password, we need the Document.prototype.get() function with an additional option.

Since we rarely need to access the   property, it is a more fitting approach to hide it by default.

Setters

Using setters, we can modify the data before it is populated in the database. With them, we can inject additional logic and modify the data.

You might prefer to have the above logic inside your services instead of using setters. Even if that’s the case, it is good to be aware of the above functionality.

Mongoose virtuals

A Virtual is an additional property of a document. We can get it and set it, but it does not persist in the MongoDB database.

A typical example might be with names. First, let’s create a   property without the use of virtuals.

​The above approach has a few issues. By persisting the   data into MongoDB, we duplicate the information, which is unnecessary. Also, imagine that at some point, we would like to support a middle name and make it a part of the  . A more fitting approach would be to attach the   dynamically.

Getters

We can do the above with the use of virtuals. Let’s create it along with a getter.

Now, every time we pull the user entity from the database, we also create the   property dynamically.

Similarly to regular getters, we also need to pass additional options to the Document.prototype.get() constructor. Thanks to adding   above, our virtual properties are visible when converting to JSON.

Now, when we inspect our database, we can see that the   field is not populated:

Setters

Virtuals also support setters. With them, we can set multiple properties at once.

In the above example, we set the   and   properties based on the  . Within the setter function of a Virtual, this refers to the document.

Populating virtuals

One of the most useful features of virtuals is populating documents from another collection.

In the fifth part of this series, we discuss the relationships between documents. The direction of the reference is an essential mention in the above article. Let’s bring one of the examples again:

In the above schema, we keep the reference to users in the Post document. Therefore, the documents look like that:

Once we settle for one of the above approaches to storing references, we don’t have the information on the other side of the relationship. If we store the reference inside of a post, the user document does not hold the information about posts:

We could do it the other way around instead.

Doing so would mean that the posts do not contain information about the authors. We could implement two-way referencing, as described in the mentioned article, but it comes with some disadvantages.

Solving the issue with virtuals

To tackle the above problem, we can implement a virtual property:

In the above code, we connect the   of a user to the   of a post. We also tell Mongoose through  wich model to populate documents from.

The only thing that’s left is to use the   function.

src/user/user.controller.ts

You might want to implement some additional authorization to prevent every authenticated user to have the access to all the users

An important issue that we should address here is that the   function takes some additional time to finish. A way to tackle this issue is to implement an additional query param.

src/user/user.controller.ts

Now we populate the posts array only if it was explicitly asked for through query parameters by calling  .

Summary

In this article, we’ve gone through the idea of virtuals. To fully grasp the concept, we’ve investigated getters and setters first. Thanks to doing so, we’ve managed to improve our code. By populating the virtual properties, we’ve found another solution to a common issue with references. Doing all of the above gives us lots of new possibilities on how to tackle everyday challenges.

Series Navigation<< TypeScript Express tutorial #12. Creating a CI/CD pipeline with Travis and HerokuTypeScript Express tutorial #14. Code optimization with Mongoose Lean Queries >>
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
vietphong
vietphong
1 year ago

Thank for the helpful article.