How to build a conditional query in Mongoose?

11,171

Solution 1

You don't need to call Query#where repeatedly, since you can pass all the conditions to Mongoose Model#find as:

var filteredQuery = {},
  acceptableFields = ['gender', 'race', /* etc */ ];

acceptableFields.forEach(function(field) {
  req.query[field] && filteredQuery[field] = req.query[field];
});

var query = Character.find(filteredQuery);

You'll also want to sanitize req.query depending on the allowed parameters you have in mind.

Solution 2

I am using destructing of objects for this task.

const filter = req.query.filter ? { _id: { $in: req.query.filter.split(',') } } : {};
const category = req.query.category ? { category: req.query.category } : {};
// more variables that you need and are empty objects if don't exist

const all = await Post.find({ ...filter, ...category }).exec();

Solution 3

Well,

I would recommend something like this:

var query = Character.find()
if(req.params.length < 0) {
  for(var key in req.params) {
    query.where(req.params[key]).equals(key);
  }
} else {
  // do something without query params
}

this is not tested by me but it should work (maybe you need to modify it a bit but you get the idea). This solution is all about not checking what actually is in the params so be sure only good stuff comes in or validate it somewhere in the for loop, but would require some regex or if statements.

Hope this helps you.

Share:
11,171
Sahat Yalkabov
Author by

Sahat Yalkabov

Updated on June 16, 2022

Comments

  • Sahat Yalkabov
    Sahat Yalkabov almost 2 years

    The following code works with no querystrings or one querystring only. In other words, simply going to /characters returns all characters. But if you were to specify a querystring parameter /characters?gender=male it will return only male characters.

    How could I extend this to work with either 1, 2, 3, or no querystrings? I would really prefer to avoid writing 8 or 9 different if-statements for each case. I was hoping Mongoose would simply ignore a $where clause if it's null or undefined, but that is not the case (see commented out code).

      var gender = req.query.gender;
      var race = req.query.race;
      var bloodline = req.query.bloodline;
    
      var query = Character.find();
    
      if (gender)
        query = query.where('gender').equals(gender);
      if (race)
        query = query.where('race').equals(race);
      if (bloodline)
        query = query.where('bloodline').equals(bloodline);
    
      /*
      query
        .where('gender').equals(new RegExp('^' + gender + '$', 'i'))
        .where('race').equals(new RegExp('^' + race + '$', 'i'))
        .where('bloodline').equals(new RegExp('^' + bloodline + '$', 'i'));
      */
    
      query.exec(function(err, characters) {
        if (err) throw err;
        res.send(characters);
      });
    

    Edit: Well I guess I could do it with 7 if-statements for now. Unless someone finds a more elegant solution.

    Edit 2:

    Thanks guys. It's difficult to pick one answer because both of you have helped me to achieve this concise solution. Here is the entire thing now.

    var conditions = {};
    
    for (var key in req.query) {
      if (req.query.hasOwnProperty(key)) {
        conditions[key] = new RegExp('^' + req.query[key] + '$', 'i');
      }
    }
    
    var query = Character.find(conditions);
    query.exec(function(err, characters) {
      if (err) throw err;
      res.send({ characters: characters });
    });
    
  • Sahat Yalkabov
    Sahat Yalkabov over 10 years
    To future visitors: Accepted answer's example does not work. I am not even using req.params, but rather req.query. Instead of chaining .where, I am building a condition object dynamically and then pass it to Character.find(). Refer to my Edit #2.