Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically building MongoDB queries in NodeJS

I receive a POST argument that looks like this:

sort:
    [ 
        { field: 'name', dir: 'asc', compare: '' },
        { field: 'org', dir: 'asc', compare: '' }
    ] 
}

and I need to create a MongoDB query based on that, so it should look like:

db.collection("my_collection").find( ... ).sort({'name': 'asc', 'org': 'asc'}).toArray(...);

Anyways, keep in mind that more fields could be passed. Also, it could happen that none of those fields is passed, meaning that the query won't have .sort().

My question: How can I create dynamically a query with Node's MongoDB driver? Is there a query builder or something similar?

like image 493
alexandernst Avatar asked Sep 10 '13 14:09

alexandernst


1 Answers

I've found that most cases are unique regarding passed data, so building query objects varies from project to project.
So first ideas was to create middleware for express (in my case), that would parse query arguments into objects that are valid for query.

mongo-native can use as chained options to cursor, as well as in object:

Chained:

items.find({ type: 'location' }).sort({ title: 1 }).limit(42).toArray(function(err, data) {
  // ...
});

Non-chained:

items.find({ type: 'location' }, { sort: { title: 1 }, limit: 42 }).toArray(function(err, data) {
  // ...
});

As you can see Non-Chained can accept everything as object, while chained returns cursor after every method and can be reused. So generally you have two options:

For Chained:

var cursor = items.find({ type: 'location' });
if (sort) {
  cursor.sort(sort);
}
cursor.toArray(function(err, data) {
  // ...
});

For Non-Chained:

var options = { };
if (sort) {
  options.sort = sort;
}
items.find({ type: 'location' }, options).toArray(function(err, data) {
  // ...
});

It is important to remember that any data from query have to be validated and parsed properly. As well if you are developing API (for example), and will decide to change the way sorting arguments are passed or will want to add new way, then making middleware (in express.js) for parsing this data - is the way to go.

Example for pagination:

function pagination(options) {
  return function(req, res, next) {
    var limit = options.limit ? options.limit : 0;
    var skip = 0;

    if (req.query.limit) {
      var tmp = parseInt(req.query.limit);
      if (tmp != NaN) {
        limit = tmp;
      }
    }
    if (req.query.skip) {
      var tmp = parseInt(req.query.skip);
      if (tmp != NaN && tmp > 0) {
        skip = tmp;
      }
    }

    if (options.max) {
      limit = Math.min(limit, options.max);
    }
    if (options.min) {
      limit = Math.max(limit, options.min);
    }

    req.pagination = {
      limit: limit,
      skip: skip
    };
    next();
  }
}

Usage:

app.get('/items', pagination({
  limit: 8, // by default will return up to 8 items
  min: 1, // minimum 1
  max: 64 // maximum 64
}), function(req, res, next) {
  var options = {
    limit: req.pagination.limit,
    skip: req.pagination.limit
  };
  items.find({ }, options).toArray(function(err, data) {
    if (!err) {
      res.json(data);
    } else {
      next(err);
    }
  });
});

And url examples:

http://example.com/items  
http://example.com/items?skip=64  
http://example.com/items?skip=256&limit=32  

So it is the way to develop well flexible framework, which does not creates any rules of how things have to be coded as well as solving your challenge.

like image 191
moka Avatar answered Oct 14 '22 22:10

moka