Reputation: 334
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
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;
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
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:
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;
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:
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;
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()
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;
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