I am using express and mongoose to list cats in my home page
app.get('/', function(req, res) {
Model.cats.find(function(err, ret) {
res.render('home.jade', {cats: ret});
})
});
Now lets say I wanted to display cats and dogs as well. How could I do it? The find function is async after all, so Im not sure how to prepare the response.
If you include the async npm module, it's quite simple:
app.get('/', function(req, res) {
async.parallel({
cats: function(callback) {
Model.cats.find(function(err, ret) {
callback(null, ret);
});
},
dogs: function(callback) {
Model.dogs.find(function(err, ret) {
callback(null, ret);
});
}
},
function(err, results) {
// results will be { cats: results, dogs: results}
res.render('home.jade', results);
});
});
The technique above starts two named async requests, one cat and the other dog. Each function calls your Models. When complete, they call the callback passing any errors (which I've just put as null above) and the results of the find. It's quite handy when using named calls this way as the results will be placed in two properties of a single object which you could pass directly to the render method.
Related
I have a big problem.
I want to iterate over collection a result set and for each set i want to find one result.
This looks like this:
router.get('/', function(req, res) {
var floors = [];
var rooms = [];
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
});
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
req.db.collection('floors').findOne({_id: new ObjectID(room.floorId)}, function(error, floor) {
room.floor = floor;
rooms.push(room);
});
});
res.render('rooms', { floors: floors, rooms: rooms });
});
The Problem is that the page will be rendered before the iteration is complete.
I tried to use async and promises, but i didn't get it to run.
Basically you have to wait until all your queries are done before sending the rendering result. Unfortunately you don't use promises so this will get a bit messy.
It appears that you are using the native client and according to the docs there is a second callback that gets called when all iterations are done
http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#forEach
router.get('/', function(req, res, next) {
var floors = [];
var rooms = [];
function done(err){
if(err) {
return next(err);
}
res.render('rooms', { floors: floors, rooms: rooms });
}
function getRooms(err){
if(err){
return next(err);
}
req.db.collection('rooms').find().sort({_id: 1}).forEach(function(room) {
// you already have all the floors, no need to hit the db again
floors.find(floor => floor._id === room.floorId); // not sure about this 100% as _id could be an object
}, done);
}
req.db.collection('floors').find().sort({_id: 1}).forEach(function(floor) {
floors.push(floor);
}, getRooms);
});
to be noted that this request will get quite heavy when your db grows.
I am trying to run two different mongoose queries and display the result in same ejs page. I searched a lot and finally found the async method. How I tried this is below.
js file
router.get('/CreateSurvey', function(req, res, next) {
async.parallel([
function(callback) {
QBank.find( function ( err, classes, count ){
classes : classes
});
},
function(callback) {
QBank.findOne({_id:"H001"}, function ( err, questions, count ){
questions : questions
});
}
], function(err) {
if (err) return next(err);
res.render('CreateSurvey', QBank);
});
});
But when I refresh '/CreateSurvey' page it does not render. I have already downloaded async module and required in my js file. Where am I wrong?
Firstly, What is that classes : classes and questions : questions lines in your code? What are you trying to do?
The callback parameter of each task function (the functions in array) should be called inside the task to indicate completion of each task with success or error.
The optional main callback (the third function in your code), where you are doing the actual rendering, will be called only after all the tasks have completed.
In your code callback is not called inside the task. As a result the final callback is also never getting called. In fact, in your code only the first *task gets executed and nothing happens beyond that.
Read more about async.parallel in documentation
There are some issues with the code, you aren't calling callback corresponding to async call. Try the following:
router.get('/CreateSurvey', function(req, res, next) {
async.parallel({
classes: function(callback) {
QBank.find(function(err, classes, count){
callback(classes);
});
},
questions: function(callback) {
QBank.findOne({_id:"H001"}, function (err, questions, count ){
callback(questions);
});
}
}, function(err, result) {
if (err) return next(err);
res.render('CreateSurvey', result); // { classes : [c1, c2] , questions : [q1, q2] }
});
});
I'm making a CRUD List App using Mongo/Express and having callback trouble!
I have a routes file with my HTTP methods and a services file that pulls in my mongoose model, Item.
Because this is from a class I'm taking I already have a GET and POST that are running correctly, but having a total headache trying to get my DELETE working.
Routes.js
router.post('/items', function(req, res) {
Item.save(req.body.name, function(item) {
res.status(201).json(item);
}, function(err) {
res.status(400).json(err);
});
});
router.delete('/items/:id'), function(req, res) {
var ref = req.params.id;
Item.remove(ref, function(err, item){
res.json(item);
})
};
Services.js
exports.save = function(name, callback, errback) {
Item.create({ name: name }, function(err, item) {
if (err) {
errback(err);
return;
}
callback(item);
});
};
exports.remove = function(id, callback){
Item.findByIdAndRemove(id, callback())
};
Obviously my delete/remove is still pretty CRUD.... Included the POST for reference. Help would be amazing as attempts at translating existing examples into my architecture have been total fails!
Your punctual problem is that you execute the callback instead of passing it into the mongoose method in the remove function:
exports.remove = function(id, callback){
Item.findByIdAndRemove(id, callback())
};
should be, so that mongoose can call it:
exports.remove = function(id, callback){
Item.findByIdAndRemove(id, callback)
};
I wonder why you feel it necessary to wrap the the mongoose methods findByIdAndRemove, and create with your own and to put them in a service? The new methods don't seem to add any value.
If you want to do things before or after deleting the model, it would be more idiomatic to trap the mongoose model life-cycle methods using middleware: http://mongoosejs.com/docs/middleware.html.
I'm usting Nodejs Expressjs MongoDB and Mongoose to create rest API for a small service-app I work on.
I did all routes applying single straightforward functions like .find()
.findOneAndUpdate() and etc.
Like this one:
router.get('/testTable', function(req, res, next) {
TestModel.find(function (err, testTableEntries) {
if (err) return next(err);
res.send(testTableEntries);
});
});
Pretty simple. But, what if I want to incorporate more functions then just single mongo function .find()
What if I want to:
.find().pretty()
Or if want to aggregate, make some counts:
.find().count()
.find({"fieldName": "How many have this value?"}).count()
.find({"fieldName": "How many have this value?"}).count().pretty()
I tried something like:
router.get('/testTable', function(req, res, next) {
TestModel.find(function (err, testTableEntries) {
if (err) return next(err);
res.send(testTableEntries);
}).count();
});
Or maybe you can advice callbackless solution with promise (like Bluebird), my first thought is:
router.get('/testTable', function(req, res, next) {
TestModel.find(function (err, next) {
if (err) return next(err);
}).then(function (req, res, next) {
res.send(testTableEntries);
}).catch(function (err, next) {
if (err) return next(err);
});
});
Maybe there's some Mongoose built-in functions that can solve it, I'll be grateful, but also would be useful to know how to call functions in chain one after another on Mongoose Models.
I'm grateful in advance for any advises and thoughts!
Well you can always just call .count() directly:
Test.count({"fieldName": "How many have this value?"},function(err,count) {
// count is the counted results
});
And of course the object returned from `.find() in mongoose is an array, so:
Test.find({"fieldName": "How many have this value?"},function(err,results) {
var count = results.length;
})
Or for "chaining" you can do this with "query" .count()
Test.find({"fieldName": "How many have this value?"})
.count().exec(function(err,results) {
})
And if you want the "counts" of all possible "fieldName" values then use .aggregate():
Test.aggregate([
{ "$group": {
"_id": "fieldName",
"count": { "$sum": 1 }
}},
],function(err,results) {
// each result has a count
});
You generally need to start thinking in terms of "working inside" the callback in async programming and not "returning" results from your method call to an "outside" variable.
given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param