Shrikant
Shrikant

Reputation: 334

collect data from multiple Mongoose queries and sent to route

Just started learning Express with content available online. With pages depending on single query I'm able to manage. But I'm stuck with page where I have to build a pagination table. Here I have to call 2 queries first for total count and second for 1st page (limit 20) of dataset. I'm using Bootstrap tables which renders data after page loads through ajax call. Response should be in following format -

{"total":100,"rows":[{"id":"59af010d93f3cc9ab0e09b3a","email":"[email protected]"},
{"id":"59af010d93f3cc9ab0e09b3a","email":"[email protected]"}]}}

I have a route file - users.js where I'm calling my model - users.js

routes/users.js

const User = require('../models/user');

router.get('/userslist', (req, res) => {
  User.getUserList(req, (err, user,next) => {
    if(err) throw err;
    if(!user){
      return res.json({success: false, msg: 'User not found'});
    }
      res.json({"rows":user});
  });  
});
module.exports = router;

models/user.js

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

module.exports.getUserList = function(req, callback){

    skip = parseInt(req.query.offset);
    nPerPage = parseInt(req.query.limit);
    sortby = req.query.sort;
    orderby = req.query.order;

    User.find().exec(function (err, count){
          User.find(callback).sort([[sortby, orderby]]).skip(skip > 0 ? (skip+1) : 0).limit(nPerPage).exec(function(err, data){
            res.json( {"total":count.length,"rows":data} );
          });
})

In this model file I'm trying to call 2 queries together and thats what is throwing error. But when I only call second query without res.json it works.

Please tell me what is the best practice to call multiple queries and also how can I improve my understanding on callbacks. I feel that's where I'm totally blank. If you can suggest any online content with easy examples, I'll be grateful.

Upvotes: 0

Views: 727

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151122

That's not the correct way to issue the callback. And your big issue was res is not in scope. But there are other things you really should be paying attention to.

Instead you pass the result ( data and/or error ) to the calling function and let it be resolved there:

routes/users.js

const User = require('../models/user');

router.get('/userslist', (req, res, next) => {
  User.getUserList(req, (err, data) => {
    if(err) next(err);
    if(!data){
      return res.json({success: false, msg: 'User not found'});
    }
    res.json(data);
  });  
});
module.exports = router;

models/user.js

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

module.exports.getUserList = function(req, callback) {

    skip = parseInt(req.query.offset);
    nPerPage = parseInt(req.query.limit);
    sortby = req.query.sort;
    orderby = req.query.order;

    User.count().exec((err, count) => {
      User.find().sort([[sortby, orderby]])
        .skip(skip > 0 ? (skip+1) : 0).limit(nPerPage)
        .exec((err, data) => {
          callback(err,{"total":count,"rows":data});
        });
    })
}

Or better yet, learn to use promises:

routes/users.js

const User = require('../models/user');

router.get('/userslist', (req, res, next) => {
  User.getUserList(req).then(data => {
    if(!data) {
      return res.json({success: false, msg: 'User not found'});
    }
    res.json(data);
  }).catch(next);
});
module.exports = router;

models/user.js

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

module.exports.getUserList = function(req) {

    skip = parseInt(req.query.offset);
    nPerPage = parseInt(req.query.limit);
    sortby = req.query.sort;
    orderby = req.query.order;

    return User.count().then(count =>
      User.find().sort([[sortby, orderby]])
        .skip(skip > 0 ? (skip+1) : 0)
        .limit(nPerPage).then(data => ({"total":count,"rows":data}))
    );

}

Or even modernizing with async/await, and making both the count and data request in parallel with Promise.all()

routes/users.js

const User = require('../models/user');

router.get('/userslist', async (req, res, next) => {
  try {
    let data = await User.getUserList(req)
    if(!data) {
      return res.json({success: false, msg: 'User not found'});
    }
    res.json(data);
  } catch(e) {
    next(e);
  }
});
module.exports = router;

models/user.js

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;

module.exports.getUserList = (req) => {

    skip = parseInt(req.query.offset);
    nPerPage = parseInt(req.query.limit);
    sortby = req.query.sort;
    orderby = req.query.order;

    return Promise.all([
      User.count().then(count => ({ total: count })),
      User.find().sort([[sortby, orderby]])
        .skip(skip > 0 ? (skip+1) : 0)
        .limit(nPerPage)
        .then( data => ({ rows: data }))
    ]).then(result => result.reduce((acc,curr) =>
      Object.assign(acc,curr),{})
    );

}

In either case, it's not the task of you model function to return data through the controller. Instead you pass the data back and let the method on the controller actually perform the output.

Also note using .count() is preferable to .find() and then getting the length of the resulting array. The former is far more efficient as it gets the result count from the "cursor" without actually needing to iterate and return all the documents from the cursor, which is what the latter is doing.

Upvotes: 2

Related Questions