Mongoose: How to query after populate() (with examples)

Updated: December 30, 2023 By: Guest Contributor Post a comment

Introduction

Mongoose is a popular ODM library for MongoDB that provides a straightforward, schema-based solution to model your application data. Its populate mechanism allows developers to create a reference to other documents in other collections and, when executing a query, replace the references with the actual documents. Although immensely useful, developers often encounter scenarios where they need to further query or filter the resulting populated documents. This tutorial provides an in-depth guide on how to use Mongoose queries after the populate() function, implementing basic to advanced examples utilizing the latest JavaScript and Node.js features like async/await and arrow functions.

Basics of Populate

Example 1: Basic Population

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const authorSchema = new Schema({
  name: String
});
const bookSchema = new Schema({
  title: String,
  author: { type: Schema.Types.ObjectId, ref: 'Author' }
});

const Author = mongoose.model('Author', authorSchema);
const Book = mongoose.model('Book', bookSchema);

async function getBooks() {
  const books = await Book.find().populate('author');
  return books;
}

getBooks().then(books => console.log(books));

We began with a simple mongoose schema where a ‘Book’ has a reference to an ‘Author’. After finding books, we populate the author details inside the book results.

Filtering after Populate

Example 2: Filtering Books by Author’s Name

async function getBooksByAuthorName(authorName) {
  const books = await Book.find()
    .populate({
      path: 'author',
      match: { name: authorName },
      // Here more complex matches or filters could be applied
    });

  // Filter out books whose author field was not populated,
  // which happens when the match fails
  return books.filter(book => book.author);
}

getBooksByAuthorName('J.K. Rowling').then(books => console.log(books));

This example showcases how to filter the populated documents by an author’s name.

Advanced Query Techniques

Example 3: Complex Queries with aggregate()

async function getBooksByAuthorWithAggregate(authorName) {
  return Book.aggregate([
    {
      $lookup: {
        from: 'authors',
        localField: 'author',
        foreignField: '_id',
        as: 'authorDetails'
      }
    },
    {
      $match: { 'authorDetails.name': authorName }
    },
    {
      $unwind: '$authorDetails'
    }
    // Additional aggregation stages can be included here,
    // such as sorting, grouping, etc.
  ]);
}

getBooksByAuthorWithAggregate('Isaac Asimov').then(books => console.log(books));

In a more complex scenario, where you need to perform operations not supported by populate(), you can use aggregate() with a $lookup stage to join collections and follow it with further filtering or transformations.

Conclusion

In conclusion, Mongoose’s populate() can be exceptionally powerful when combined with the right query operators and techniques. Developing an understanding of how to filter and manipulate data post-population results in uncompromised control over your queries, ensuring you can mold the data to fit the needs of your application. By starting with the basics and gradually delving into more complex aggregation procedures, this article aims to equip you with the necessary understanding to proficiently utilize Mongoose in real-world scenarios. Remember that efficiency and performance considerations should also be weighed when deciding between a straightforward populate() and a full-fledged aggregate() approach.