I am new to NodeJS(Express). I am trying to build an API that will list the "Rooms" of a Hotel, and "Photos" for each room.
What I am trying to do is: - get a list of all rooms by a hotel - loop through this list of rooms and add photos - return JSON with rooms and photos
My problem is that I can not seem to attach the "photos" result to the "rooms result.
async.waterfall([
// Function 1
function(callback) {
// Find all Hotel Rooms
Room.find({ 'hotel': req.params.hotel_id})
.exec(function(err, rooms) {
callback(null, rooms); // Send rooms to function 2
});
},
// Function 2
function(rooms, callback) {
// Loop through each room to get photos for each room
rooms.forEach(function(room) {
// Get photos for room
RoomPhoto.find({ 'room': room._id})
.exec(function(err, photos) {
// I want to add these "photos" to the main "rooms" object, but attached to each individual "room"
room.photos = JSON.stringify(photos);
});
})
console.log(rooms)
callback(null, rooms);
}
], function(err, results) {
if(err) console.log('err');
res.json(results);
});
Any help would be greatly appreciated, as I have been hours trying to figure it out.
Thanks
Ray
I have updated my script to this, but the "photos" are still not going into the final "rooms" object. I have included the code and also the console output.
Code:
exports.index = function(req, res, next) {
Room.find({ 'hotel': req.params.hotel_id})
.exec(function(err, rooms) {
async.each(rooms, function(room, room_cb) {
console.log('Room Name: ' + room.name);
RoomPhoto.find({ 'room': room._id})
.exec(function(err, photos) {
console.log('Photos: ' + photos);
room.photos = photos;
// photos should now be in the main "rooms" object right??
// I can console.log the photos here to prove they exists
room_cb();
});
}, function(err) {
if(err) {
console.log('Error: ' + err)
}
else {
// But the photos are not in the final "rooms" object
console.log('FINAL ROOMS OBJECT, why are the photos not here??');
console.log(rooms);
res.json(rooms);
}
})
});
};
Here is the console output for the above script:
Room Name: Test Room 1
Room Name: Test Room 2
Photos:
{ _id: 5acd3094e0026f38ca4b44bc, src: 'https://picsum.photos/200/300/?image=252', room: 5acd3094e0026f38ca4b44bb, __v: 0 }Photos:
{ _id: 5acd30c8cec87777eefcd32d, src: 'https://picsum.photos/200/300/?image=252', room: 5acd30c8cec87777eefcd32c, __v: 0 }FINAL ROOMS OBJECT
[ { _id: 5acd3094e0026f38ca4b44bb, name: 'Test Room 1', hotel: 5accd4e1734d1d55c3195c84, __v: 0 }, { _id: 5acd30c8cec87777eefcd32c, name: 'Test Room 2', hotel: 5accd4e1734d1d55c3195c84, __v: 0 } ]
4 Answers 4
Since the function RoomPhoto is also asynchronous, then you need to wait until all the photos are loaded before calling the callback function. Like that:
// Function 2
function(rooms, callback) {
// https://caolan.github.io/async/docs.html#each
// Loop through each room to get photos for each room
async.each(rooms, function(room, room_cb) {
// Get photos for room
RoomPhoto.find({ 'room': room._id})
.exec(function(err, photos) {
room.photos = JSON.stringify(photos)
room_cb()
});
}, function (err) {
console.log(rooms)
callback(null, rooms)
})
}
5 Comments
async.eachOf - caolan.github.io/async/docs.html#eachOf - rooms[key].photos = photos...I found something that works... For some reason, the MongoDB "_id" was causing a problem.
exports.index = function(req, res, next) {
Room.find({ 'hotel': req.params.hotel_id})
.exec(function(err, rooms) {
rooms = JSON.parse(JSON.stringify(rooms).replace(/_id/g,"room_id"));
console.log(rooms);
console.log('---------------------------');
async.each(rooms, function(room, room_cb) {
RoomPhoto.find({ 'room': room.room_id})
.exec(function(err, photos) {
console.log('Room: ' + room.id);
console.log('Photos: ' + photos);
room.photos = photos;
//console.log('Photos: ' + rooms[key].photos);
room_cb();
});
}, function(err) {
if(err) {
console.log('Error: ' + err)
}
else {
console.log('FINAL ROOMS OBJECT');
rooms = JSON.parse(JSON.stringify(rooms).replace(/room_id/g,"_id"));
console.log(rooms);
res.json(rooms);
}
})
});
};
Comments
Since you are new to node.js you may not be aware of the latest standards, and things you can use.. Here's how you can accomplish it without including async module at all...
const requestHandler = async (req, res, next) => {
let rooms = await Room.find({ 'hotel': req.params.hotel_id})
.exec()
.catch(err => {
console.log(err);
throw err;
});
// Get photos for room
let photos = await Promise.all(rooms.map(
room => RoomPhoto.find({ 'room': room._id})
.catch(err => {
console.log(err);
throw err;
});
))
rooms = rooms.map((room, index) => {
room = room.toObject();
room.photos = JSON.stringify(photos[index]);
return room;
});
// Now your "rooms" array will have room objects, with photos populated.
res.send(rooms);
});
To export it you can do it like this:
module.exports.index = requestHandler;
7 Comments
nodejs version and expressjs versionTry placing your second callback(null, rooms); line (the one near the end) under the room.photos = JSON.stringify(photos); line.
Because of the asynchronous nature of JavaScript, it is probably running before your query has even finished getting the photos.
You might also want to add some checks in case there are no photos for a room.