Mongoose: How to filter documents by multiple fields

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

Introduction

Mongoose is a powerful Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a higher-level abstraction over MongoDB’s native operations. Filtering documents based on criteria is among the key features of any database system, and Mongoose offers versatile ways to accomplish this. In this guide, you’ll learn how to filter documents by multiple fields in Mongoose effectively, moving from basic to more advanced queries for fine-tuned search functionality.

Basic Query Filtering

const mongoose = require('mongoose');
const { Schema } = mongoose;

const userSchema = new Schema({
  name: String,
  age: Number,
  status: String
});

const User = mongoose.model('User', userSchema);

async function findActiveAdults() {
  const adults = await User.find({ age: { $gte: 18 }, status: 'active' });
  return adults;
}

In the code sample above, we’re filtering documents that represent adult users with an ‘active’ status. The $gte operator is used to match all users who are 18 or older. This is a straightforward query that uses MongoDB’s comparison query operators.

Complex Queries with Logical Operators

async function complexQuery() {
  const users = await User.find({
    $and: [
      { age: { $gte: 18 } },
      { $or: [{ status: 'active' }, { status: 'pending' }] }
    ]
  });
  return users;
}

Logical operators such as $and, $or, and $not enable the combination of multiple query conditions. As shown, we are looking for users who are at least 18 years old and have a status of either ‘active’ or ‘pending’.

Using Query Helpers for Readability

userSchema.query.byStatus = function(status) {
  return this.find({ status: new RegExp(status, 'i') });
};

async function getByStatus(status) {
  const users = await User.find().byStatus(status);
  return users;
}

Custom query helpers can be added to a schema to encapsulate complex logic. Here, a method byStatus is added to filter users by their status using a case-insensitive regular expression.

Filtering with Population

const orderSchema = new Schema({
 user: { type: Schema.Types.ObjectId, ref: 'User' },
 completed: Boolean
});

const Order = mongoose.model('Order', orderSchema);

async function findCompletedOrdersByActiveUsers() {
  const completedOrders = await Order.find({ completed: true }).populate({
    path: 'user',
    match: { status: 'active' }
  });
  return completedOrders.filter(order => order.user);
}

We’re querying for completed orders and simultaneously filtering out orders made by active users by leveraging Mongoose’s populate method. The filter method at the end is required to remove any orders which did not have an active user after population.

Advanced Aggregation Techniques

async function aggregateUserInfo() {
  const result = await User.aggregate([
    { $match: { age: { $gte: 18 } } },
    { $group: { _id: '$status', count: { $sum: 1 } } }
  ]);
  return result;
}

The aggregation framework provides a robust pipeline for transforming and combining documents in complex ways, such as grouping by status and counting user documents within each status. Advanced filters can also be part of the match stages within an aggregation pipeline.

Conclusion

The ability to filter documents by multiple fields in Mongoose is essential for building sophisticated data-driven applications. In this tutorial, we’ve explored a variety of methods to achieve this, such as using query conditionals, logical operators, custom query helpers, population, and aggregation. Embracing these techniques allows you to write efficient, maintainable, and powerful queries that fulfill virtually any data retrieval requirement one might encounter when working with MongoDB through Mongoose.