How to ensure exact filter matching in MongoDB with Express.js?

I’m working with Express.js to build a MongoDB API that filters products by specific criteria. I need the API to return products precisely matching the provided filter attributes. Currently, if product A has [{name: 'a', value: '1'}, {name: 'b', value: '2'}] and product B has [{name: 'a', value: '1'}, {name: 'c', value: '3'}], passing the filter [{name: 'a', value: '1'}, {name: 'b', value: '2'}] incorrectly returns product B as well. How can I enforce exact filter matches?

My product schema is as follows:

const mongoose = require('mongoose');
const { str, requiredStr, number, refObj } = require('../utils/mongo');

let schema = new mongoose.Schema({
  owner: refObj('user'),
  title: requiredStr,
  description: str,
  images: [str],
  cost: number,
  category: refObj('category'),
  criteria: [{
    parent: refObj('filter'),
    term: str,
    label: str,
  }],
  subCriteria: [{
    parent: str,
    term: str,
    header: str,
  }],
  available: Boolean,
}, { timestamps: true });

module.exports = mongoose.model('product', schema);

Here’s my query logic:

filter: async (req, res) => {
    try {
      const { categories, criteria } = req.body;
      let products;

      if (criteria.length > 0) {
        let targetedCat = categories;
        let eachFilter = criteria;

        let conditions = [];

        eachFilter.forEach(function (filter) {
          conditions.push({
            $and: [
              { $eq: [filter['name'], '$$this.name'] },
              { $eq: [filter['value'], '$$this.value'] },
            ],
          });
        });
        let combinedConditions = { $or: conditions };

        products = await Product.aggregate([
          { $match: { categories: new ObjectId(targetedCat) } },
          {
            $addFields: {
              criteria: { $filter: { input: '$criteria', cond: combinedConditions } },
            },
          },
          {
            $match: {
              $expr: { $gt: [{ $size: '$criteria' }, 0] },
            },
          },
        ]);
      } else {
        products = await Product.find({ categories });
      }

      res.status(200).json(products);
    } catch (error) {
      console.error(error);
      res.status(500).json(error);
    }
  }

The body I intend to send is:

{
  "category": "62445c3d922d127512867245",
  "filters": [
    { "name": "filter name 1", "value": "62445c3d922d127512861236" },
    { "name": "filter name 2", "value": "62445c3d922d127512861458" }
  ]
}

Sure, here’s how you can achieve exact matches in your MongoDB query when filtering products by specific attributes. Let’s adjust the query logic to ensure only products matching all specified criteria exactly are returned.

Solution:

To handle precise filter matching, you’ll want to make sure that your query only selects documents where all criteria match exactly. We can achieve this by using the $all operator, combined with $elemMatch, to ensure all specified criteria are present with exact matches.

filter: async (req, res) => {
    try {
        const { categories, filters } = req.body;

        if (filters && filters.length > 0) {
            // Map provided filters into a format compatible with $elemMatch
            const filterConditions = filters.map(filter => {
                return {
                    $elemMatch: {
                        name: filter.name,
                        value: filter.value
                    }
                };
            });

            const products = await Product.find({
                category: new ObjectId(categories),
                criteria: {
                    $all: filterConditions
                }
            });

            res.status(200).json(products);
        } else {
            const products = await Product.find({
                category: new ObjectId(categories)
            });

            res.status(200).json(products);
        }
    } catch (error) {
        console.error(error);
        res.status(500).json(error);
    }
}

Key Changes Explained:

  • $elemMatch: This operator is used to match a subset of array elements. By applying it within the $all array, you assure that all filter criteria are considered.
  • $all: This ensures that every filter condition specified must be present in the criteria field for any product to be included in the results.

Example Filter Input:
Here’s the JSON body you’d send to test this implementation:

{
  "category": "62445c3d922d127512867245",
  "filters": [
    {"name": "filter name 1", "value": "62445c3d922d127512861236"},
    {"name": "filter name 2", "value": "62445c3d922d127512861458"}
  ]
}

This approach ensures your API returns products that match every specified criterion, preventing incorrect inclusions like product B in previous examples. Try this revised code and ensure each filter is applied strictly to meet your requirements.

Hey there! To ensure precise product filtering in your MongoDB API using Express.js, you’ll want to enforce that all filter criteria match exactly. Consider applying the $all and $elemMatch operators creatively:

filter: async (req, res) => {
    try {
        const { categories, filters } = req.body;

        if (filters && filters.length > 0) {
            const filterCriteria = filters.map(filter => ({
                $elemMatch: { name: filter.name, value: filter.value }
            }));

            const products = await Product.find({
                category: new ObjectId(categories),
                criteria: { $all: filterCriteria }
            });

            res.status(200).json(products);
        } else {
            const products = await Product.find({ category: new ObjectId(categories) });
            res.status(200).json(products);
        }
    } catch (error) {
        console.error('Caught an error:', error);
        res.status(500).json(error);
    }
}

By structuring your query this way, you assure that all filters are met exactly. Give it a try!