TypeScript Express tutorial #14. Code optimization with Mongoose Lean Queries

Express JavaScript TypeScript

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

Mongoose does quite a bit of heavy-lifting for us. It is immensely useful, but not necessary in every case. In this article, we explore the Mongoose documents more and learn what we can achieve by giving up their benefits.

Mongoose Document

In the second part of this series, we’ve created our first models. They allow us to interact with our collections.

The most straightforward way of doing so is getting all the documents from our collection.

The above gives us an array of documents. Putting it simply, a document is an instance of a model.

It is essential to say that MongoDB is a document database, and we can call each record in the collection, a document. They are similar to JSON objects.

By default, Mongoose gives us more than just bare objects. Instead, it wraps them in Mongoose Documents. It gives us lots of features that might come in handy. For example, we have the   and   functions.

The above might come in handy, but it is not always crucial. Mongoose Documents have lots of things happening under the hood, and it needs both more time and memory.

The terms document and Mongoose Document are not interchangeable, and therefore this article aims to be explicit about addressing this precisely

Lean queries

When we perform a regular query to the database, Mongoose performs an action called hydrating. It involves creating an instance of the Mongoose Document using given data.

This process takes time and creates objects that weight quite a bit. To give you a better understanding, let’s investigate this handler:

  • When we call  , Mongoose queries the database to find our post
  • Once Mongoose finds the document, it creates an instance of a Mongoose Document by hydrating the raw data
  • When we call  , Express gets the raw data from the Document instance and sends it in a response

As you can see, there is quite a lot happening in such a simple handler. The most important thing to consider whether the hydrating process is necessary. Let’s rewrite the above handler a bit:

In the above code, we don’t perform the hydration, and therefore our handler is faster. Also, if you implement some manual cache, it might be a good idea to perform a lean query. An example of such a solution is the node-cache library.

The downsides of using lean documents

There are multiple things to consider, though. Before deciding to use lean queries, we need to be aware of the disadvantages of doing so.

No change tracking and saving

Instances of the Mongoose Document have quite a bit of functionality under the hood. One of the features is saving changes done to the documents.

Unfortunately, the above code would result in an error because there is no  function.

Also, Mongoose can perform typecasting on the fly.

Above, our content gets stringified on the file, because    is a setter with additional logic built into it. That isn’t a case with a lean document.

Also, if our Post has proper typings, the above operation should not be permitted

Getters and setters

In the previous part of this series, we learn about getters and setters. For example, we add a getter for the User:

By doing the above, we can easily strip out a password from the result of a database query.

Unfortunately, this would not happen with lean queries. The above is a proper example that we need to be aware of that because we could accidentally expose some sensitive data.


In the previous part of the series, we also learn about virtuals. For example, we add them to the user:

Unfortunately, virtuals are not included when using lean queries.

Default values

When using Mongoose, we can set up default values for our properties. Unfortunately, they are also not included with lean documents. The above can become troublesome if you expect a particular property to always exist in a document.

An example of the above issue are embedded documents that we mention in the fifth part of this series.

Even if we added the  property just recently, Mongoose attaches a missing empty array to the older documents on the fly. Unfortunately, this does not happen with lean queries.


Thankfully, the   function works with lean queries without issues. With it, we can effortlessly replace the id with an actual document from the database.

If you want to know more about  , check out TypeScript Express tutorial #5. MongoDB relationships between documents

Above, we can see how lean queries can cause an issue. Unfortunately, the above handler also returns a password because it is a getter. To deal with it, we can explicitly state that we want to strip it out.

In the previous article, we use virtual properties with the  function. Even though regular virtual properties don’t work with lean queries, populating them is an exception.


As seen above, lean queries come with a lot of gotchas and pitfalls. When using them, we can see the extent of work that Mongoose does for us under the hood. We might not need lean queries at all in our API, and it probably should be our focus right away. It might be a better idea to implement them when we notice some areas that are not as performant as we need.



On the other hand, it is beneficial to know what lean queries are just in case we need them. They can serve as just another tool in our toolbox, ready to use when needed. If we decide to go with them, it is important to be aware of what they bring to the table.

Series Navigation<< TypeScript Express tutorial #13. Using Mongoose virtuals to populate documentsTypeScript Express tutorial #15. Using PUT vs PATCH in MongoDB with Mongoose >>
Notify of
Inline Feedbacks
View all comments