9

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

 4 years ago
source link: https://wanago.io/2020/04/20/typescript-express-tutorial-code-optimization-mongoose-lean/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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.

const posts = await postModel.find();

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  set ( ) and  save ( ) functions.

const post = await postModel.findById(postId);
await post.set('content', 'A brand new content!').save();

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.

const post = postModel.hydrate({
  author: '5ca3a8d2af06a818d391040c',
  title: 'Lorem ipsum',
  content: 'Dolor sit amet'
});

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

private getPostById = async (
  request: Request,
  response: Response,
  next: NextFunction
) => {
  const id = request.params.id;
  const post = await this.post.findById(id);
  if (post) {
    response.send(post);
  } else {
    next(new PostNotFoundException(id));
  }
}
  • When we call  this . post . findById ( id ) , 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  response . send ( post ) , 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:

private getPostById = async (
  request: Request,
  response: Response,
  next: NextFunction
) => {
  const id = request.params.id;
  const post = await this.post.findById(id).lean();
  if (post) {
    response.send(post);
  } else {
    next(new PostNotFoundException(id));
  }
}

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.

const post = await postModel.findById(postId).lean();
post.content = 'A brand new content!';
await post.save();

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

Also, Mongoose can perform typecasting on the fly.

const post = await postModel.findById(postId);
post.content = 123;

Above, our content gets stringified on the file, because post . content 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:

const userSchema = new mongoose.Schema(
  {
    email: String,
    name: String,
    password: {
      type: String,
      get: (): undefined => undefined,
    },
  },
  {
    toJSON: {
      getters: true,
    },
  },
);

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.

Virtuals

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

userSchema.virtual('fullName').get(function () {
  return `${this.firstName} ${this.lastName}`;
});

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 .

import { Schema }rom 'mongoose';
import order from './order.schema';
 
const customer = Schema({
  name: String,
  email: String,
  orders: [order]
});

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

Populate

Thankfully,   the  populate 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 populate , check out  TypeScript Express tutorial #5. MongoDB relationships between documents

private getAllPosts = async (request: Request, response: Response) => {
  const posts = await this.post.find()
    .lean()
    .populate('author');
  response.send(posts);
}

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.

const posts = await this.post.find()
  .lean()
  .populate('author', '-password');

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

userSchema.virtual('posts', {
  ref: 'Post',
  localField: '_id',
  foreignField: 'author',
});
private getUserById = async (request: Request, response: Response, next: NextFunction) => {
  const id = request.params.id;
  const userQuery = this.user.findById(id).lean();
  if (request.query.withPosts === 'true') {
    userQuery.populate('posts');
  }
  const user = await userQuery;
  if (user) {
    response.send(user);
  } else {
    next(new UserNotFoundException(id));
  }
}

Summary

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.

JZ3euuE.png!web

xkcd.com

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK