160

Recently I start using MongoDB with Mongoose on Nodejs.

When I use Model.find method with $or condition and _id field, Mongoose does not work properly.

This does not work:

User.find({
  $or: [
    { '_id': param },
    { 'name': param },
    { 'nickname': param }
  ]
}, function(err, docs) {
   if(!err) res.send(docs);
});

By the way, if I remove the '_id' part, this DOES work!

User.find({
  $or: [
    { 'name': param },
    { 'nickname': param }
  ]
}, function(err, docs) {
   if(!err) res.send(docs);
});

And in MongoDB shell, both work properly.

Sonicd300
  • 1,950
  • 1
  • 16
  • 22
Younghan
  • 4,827
  • 3
  • 17
  • 9

3 Answers3

284

I solved it through googling:

var ObjectId = require('mongoose').Types.ObjectId;
var objId = new ObjectId( (param.length < 12) ? "123456789012" : param );
// You should make string 'param' as ObjectId type. To avoid exception, 
// the 'param' must consist of more than 12 characters.

User.find( { $or:[ {'_id':objId}, {'name':param}, {'nickname':param} ]}, 
  function(err,docs){
    if(!err) res.send(docs);
});
Draiken
  • 3,805
  • 2
  • 30
  • 48
Younghan
  • 4,827
  • 3
  • 17
  • 9
  • 4
    can you describe why this solution works with words? thanks – Alexander Mills Nov 22 '15 at 23:21
  • This looks like a solution to a rather specific issue. You may have to keep searching. – Kesarion Apr 15 '16 at 15:17
  • Could you provide your reference? Why in this case param has to consist more than 12 characters? Is this specific to your problem or the requirement of ObjectId()? What if my param doesn't have 12 characters? Thanks! – yusong Dec 02 '16 at 20:13
  • 8
    you can also check ObjectId as follow: `const mongoose = require('mongoose'); mongoose.Types.ObjectId.isValid(objectidtocheck)` – Orhan Jun 18 '17 at 02:21
  • @yusong it's due to mongoose will throw an error if it's not a valid ObjectId. A cleaner way to do it mentioned above me. – Haydar Ali Ismail Jun 19 '17 at 23:22
  • Use Joi or another validation library –  Apr 15 '20 at 02:15
91

I implore everyone to use Mongoose's query builder language and promises instead of callbacks:

User.find().or([{ name: param }, { nickname: param }])
    .then(users => { /*logic here*/ })
    .catch(error => { /*error logic here*/ })

Read more about Mongoose Queries.

Govind Rai
  • 14,406
  • 9
  • 72
  • 83
  • Love it! Thanks for the heads up! – zeckdude Apr 29 '20 at 07:31
  • Why is chaining methods like `or()` onto `find()` better than using operators like `{ $or: [] }` in a regular JSON filter? Is this way faster? Is one just syntactic sugar for another? – Benji Dec 20 '22 at 13:04
  • 1
    Yes, the methods are syntactic sugar. It's a matter of preference. For me they sometimes read better. Other times, they make things less readable. I would say to stick with whatever makes more sense to you, but stick to one or the other. Or if you're working at a company, it would be best to go with whatever the norm is at your company and stay consistent. – Govind Rai Dec 21 '22 at 06:34
6

You can also add a mix of $or and and to give your functions more flexibility, options and robustness, like so:

var ObjectId = require("mongoose").Types.ObjectId;
var idParam = new ObjectId(param.length < 12 ? "123456789012" : param);
const {nameParam, nicknameParam, ageParam} = req.params || req.body || req.query

User.find({
        $or: [{
                _id: objId
            },
            {
                name: nameParam
            },
            {
                nickname: nicknameParam
            }
        ],
        $and: [{
            age: ageParam
        }]
    },
    function (err, docs) {
        if (!err) res.send(docs);
    }
);

So this means that your find() will look for all users where (_id = idParam OR name = nameParam OR nickname = nicknameParam) AND (age = ageParam)

I hope this helps someone out there. Cheers!!!

AllJs
  • 1,760
  • 4
  • 27
  • 48