Loop with asynchronous callbacks in mongoose/mongodb/node - node.js

I am new to nodejs/mongo/mongoose and i am trying to do a very simple thing. I have the following schemas:
var authorSchema = mongoose.Schema({
name: String,
});
Author = mongoose.model('Author', authorSchema);
var bookSchema = mongoose.Schema({
title: String,
isbn: String,
pages: Number,
author: { type : mongoose.Schema.ObjectId, ref : 'Author', index: true }
});
Book = mongoose.model('Book', bookSchema);
I want to create a list of authors with id, name and book count for each author. I have something like this:
exports.author_list = function(req, res){
Author.find({}, function (err, authors){
var author_array = Array();
for (var i=0;i<authors.length;i++){
var author_obj = new Object();
author_obj.id = authors[i]._id;
author_obj.name = authors[i].name;
author_obj.count = 0; //here is the problem
author_array[i] = author_obj;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify({ authors: author_array }));
res.end();
});
}
I know how to do a query for the count. My problem is how to do a loop of the authors and populate the output with asynchronous callbacks. What is the proper way to implement this in nodejs fashion?
Thanks

I think you'd want to use something like async to coordinate those requests; map() seems to be a good choice:
Author.find({}, function (err, authors) {
async.map(authors, function(author, done) {
Book.count({author: author._id}, function(err, count) {
if (err)
done(err);
else
{
done(null, {
id : author._id,
name : author.name,
count : count
});
}
});
}, function(err, author_array) {
if (err)
{
// handle error
}
else
{
/*
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify({ authors: author_array }));
res.end();
*/
// Shorter:
res.json(author_array);
}
});
});

Related

How to use findByIdAndUpdate on mongodb?

I am a noobie in coding and I am having an issue with how to use properly MongoDB. I have a parent object classroom containing an array of objects - comments. I am trying to update the content of 1 selected comment.
originally I updated the state of the whole "classroom" in the react and passed all the data and $set {req.body} in findByIdAndUpdate.
I want to achieve the same result if I only pass to my axios request classId, commentId and comment data and not whole classroom / all comments
I tried to filter selected comment out of the array of comments and concat updated comment, but that did not work. Clearly, I have any idea what is going on and docs don't make it any easier for me to understand.
my classroom schema:
var ClassroomSchema = new Schema({
title: String,
teacher: String,
info: String,
image_url: String,
comments: [Comment.schema]
});
comment schema:
var CommentSchema = new Schema()
CommentSchema.add({
content: String,
comments: [CommentSchema],
created_at: {
type: Date,
default: Date.now
}
});
original solution:
function update(req, res){
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log(req.body.comments)
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set: req.body}, function(err, classroom){
if (err) {
console.log(err);
res.send(err);
} else {
commentToUpdate = req.body.commentData;
res.json(classroom);
}
});
});
}
my current failing atempt:
function update(req, res){
console.log('update => req.body: ', req.body);
console.log('req.params', req.params)
Comment.findById(req.params.comment_id, function(err, comment) {
if(err) res.send(err)
comment.content = req.body.content;
comment.save();
console.log('comment: ', comment);
Classroom.findById(req.params.classroom_id, function(err, classroom) {
console.log('CLASSROOM findByIdAndUpdate classroom: ', classroom)
// console.log('reg.body: ', req.body)
if (err) {
console.warn('Error updating comment', err);
res.send(err);
} else {
// commentToUpdate = req.body.commentData;
old_comments = classroom.comments;
console.log('comments: ', old_comments);
Classroom.findByIdAndUpdate(req.params.classroom_id,
{$set:
{ comments: old_comments.filter(comt._id !== comment._id).concat(comment)}
}, function(err, updatedClassroom) {
if (err) {
console.warn(err);
} else {
res.json(updatedClassroom);
}
});
}
});
});
}
haven't tested, but try this.
function update(req, res) {
Classroom.update(
{ _id: req.params.classroom_id, "comments._id": req.params.comment_id },
{ $set: { "comments.$.content": req.body.content } },
function(err) {
..
}
);
}

Recursive mongoose model doesn't properly store my data

While studying node I am trying to create a little commentthread application. Since the book I am using is a little outdated, I had to adjust the model provided in the book to get the application running. However, I think something is still wrong with my model, because a part of the data is stored as object Object. Does anyone see the problem in the model?
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ReplySchema = new Schema();
ReplySchema.add({
username: String,
subject: String,
timestamp: { type: Date, default: Date.now },
body: String,
replies:[ReplySchema]
}, { _id: true });
var CommentThreadSchema = new Schema({
title: String,
replies:[ReplySchema]
});
mongoose.model('Reply', ReplySchema);
mongoose.model('CommentThread', CommentThreadSchema);
The result in Mongo:
{ _id: 56c8c91b011c7db2608159d6,
'[object Object]timestamp': Sat Feb 20 2016 21:14:19 GMT+0100 (CET), '[object Object]replies': [] }
The controller
var mongoose = require('mongoose'),
CommentThread = mongoose.model('CommentThread'),
Reply = mongoose.model('Reply');
exports.getComment = function(req, res) {
CommentThread.findOne({ _id: req.query.commentId })
.exec(function(err, comment) {
if (!comment){
res.json(404, {msg: 'CommentThread Not Found.'});
} else {
res.json(comment);
}
});
};
exports.addComment = function(req, res) {
CommentThread.findOne({ _id: req.body.rootCommentId })
.exec(function(err, commentThread) {
if (!commentThread){
res.json(404, {msg: 'CommentThread Not Found.'});
} else {
var newComment = Reply(req.body.newComment);
newComment.username = generateRandomUsername();
addComment(req, res, commentThread, commentThread,
req.body.parentCommentId, newComment);
}
});
};
function addComment(req, res, commentThread, currentComment,
parentId, newComment){
if (commentThread.id == parentId){
console.log(newComment);
commentThread.replies.push(newComment);
updateCommentThread(req, res, commentThread);
} else {
for(var i=0; i< currentComment.replies.length; i++){
var c = currentComment.replies[i];
if (c._id == parentId){
c.replies.push(newComment);
var replyThread = commentThread.replies.toObject();
updateCommentThread(req, res, commentThread);
break;
} else {
addComment(req, res, commentThread, c,
parentId, newComment);
}
}
}
};
function updateCommentThread(req, res, commentThread){
CommentThread.update({ _id: commentThread.id },
{$set:{replies:commentThread.replies}})
.exec(function(err, savedComment){
if (err){
res.json(404, {msg: 'Failed to update CommentThread.'});
} else {
res.json({msg: "success"});
}
});
}
function generateRandomUsername(){
//typically the username would come from an authenticated session
var users=['Mojos', 'Milo', 'Mihaal', 'Haly', 'MilodeBaesz', 'Mihaly'];
return users[Math.floor((Math.random()*5))];
}

Removing references in one-to-many and many-to-many relationships in MongoDB using Mongoose and Node.js

I need to mention that I am totally aware of the fact that MongoDB is not a relational database in the first place. However it supports referencing other documents, hence some functionality should be supported, imo. Anyways, I have this relationship: a Company has many Departments and one Department belongs to one Company.
company.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
departments: [{
type: Schema.Types.ObjectId,
ref: 'Department'
}],
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Company', CompanySchema);
department.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var DepartmentSchema = new Schema({
name: {
type: String,
required: true
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company'
},
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Department', DepartmentSchema);
Now, I am writing Node.js logic to manipulate this data using API. I get that if I create a new Department, I should add a reference to Company and I should create its reference in this Company's departments array. Simple. But what if a user changes the Company property of a Department? Say, the HR Department used to belong to Company A, but a user now moves it to Company B? We need to remove the reference to this department from Company A's array and push it to Company B. The same is when we want to delete a department. We need to find a company it belongs to and dis-associate it. My solution is working ATM, but seems rather clumsy.
routes.js
var Department = require('../../models/department'),
Company = require('../../models/company');
module.exports = function(express) {
var router = express.Router();
router.route('/')
.get(function(req, res) {
// ...
})
.post(function(req, res) {
// ...
});
router.route('/:id')
.get(function(req, res) {
// ...
})
.put(function(req, res) {
// First we need to find the department with the request parameter id
Department.findOne({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
var department = data;
// department.name = req.body.name || department.name; Not relevant
// If the company to which the department belongs is changed
if (department.company != req.body.company._id) {
// We should find the previous company
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Loop through its departments
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
// And splice this array to remove the outdated reference
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
});
// Now we find this new company which now holds the department in question
// and add our department as a reference
Company.findOne({ _id: req.body.company._id }, function(err, data) {
if (err) return res.send(err);
var company = data;
company.departments.push(department._id);
company.save(function(err) {
if (err) return res.send(err);
});
});
}
// department.company = req.body.company._id || department.company; Not relevant
// department.dateUpdated = undefined; Not relevant
// And finally save the department
department.save(function(err) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department updated successfully.' });
});
});
})
.delete(function(req, res) {
// Since we only have id of the department being deleted, we need to find it first
Department.findOne({ _id: req.params.id}, function(err, data) {
if (err) return res.send(err);
var department = data;
// Now we know the company it belongs to and should dis-associate them
// by removing the company's reference to this department
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Again we loop through the company's departments array to remove the ref
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
// I guess it should be synchronously AFTER everything is done,
// since if it is done in parallel with Department.findOne(..)
// piece, the remove part can happen BEFORE the dep is found
Department.remove({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department deleted successfully.' });
});
});
});
});
return router;
};
Is there any elegant solution to this case or it is just as it should be?
I see you have not yet captured the essence of the async nature of node.js ... for example you have a comment prior to department.save which says : and finally ... well the earlier logic may very will be still executing at that time ... also I strongly suggest you avoid your callback approach and learn how to do this using promises

Saving Items In Mongoose For Loop With Schema Methods

I'm having a problem saving items running through for loop if I attach any extra validation methods. Basically, I'm building an instagram API app that allows editors to remove photos that are unseemly. Photos are pulled from Instagram in batches of 20 and displayed to editors. If an editor clicks a photo, it is first put on a 'blacklist' database by ID, then deleted from the main photo database.
In order to not have blacklisted photos reappear on the feed, before saving an item to the main photo database, it needs to check the Instagram ID of the photo against the blacklist. To do this, I'm using a Schema method.
The problem right now, is that I'm only getting ONE photo saved to the DB. If I take out the method check, then I get all 20.
Here's my main create controller:
exports.create = function(req, res) {
var topic = req.body.topic || 'nyc';
var path = 'https://api.instagram.com/v1/tags/' + topic + '/media/recent?client_id=' + 'XXXXXXXXXX';
request.get({url: path}, function(err, response){
if (err){
console.log('Failed to get data: ', err);
return res.status(400).json({error: 'Not allowed'});
}
else{
// ------------------------------------------------
// Make sure we have JSON
//
var body = JSON.parse(response.body);
// ------------------------------------------------
// Loop through each response
//
for (var i = 0; i < body.data.length; i++ ){
var photoData = body.data[i];
// ------------------------------------------------
// If there is no caption, skip it
//
if (!photoData.caption){
text = '';
}
else{
text = photoData.caption;
}
// ------------------------------------------------
// Create new photo object
//
var photo = new Photo({
link: photoData.link,
username: photoData.user.username,
profilePicture: photoData.user.profile_picture,
imageThumbnail: photoData.images.thumbnail.url,
imageFullsize: photoData.images.standard_resolution.url,
caption: text,
userId: photoData.user.id,
date: photoData.created_time,
_id: photoData.id
});
photo.checkBlacklist(function(err, blacklist){
if (!blacklist){
photo.save(function(err, item){
if (err){
console.log(err);
}
console.log('Saved', item);
})
}
});
// -------------------------------------------------
//
// Save
//
// -------------------------------------------------
} // END FOR LOOP
console.log('Photos saved');
return res.json(201, {msg: 'Photos updated'} );
}
});
};
And here's my Schema for photos:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var Blacklist = require('../blacklist/blacklist.model');
var PhotoSchema = new Schema({
created: {type: Date, default: Date.now()},
date: String,
link: String,
username: String,
profilePicture: String,
imageThumbnail: {type: String, unique: true},
imageFullsize: String,
caption: String,
userId: String,
_id: {type: String, unique: true}
});
PhotoSchema.methods.checkBlacklist = function(callback){
return Blacklist.findById(this._id, callback);
};
module.exports = mongoose.model('Photo', PhotoSchema);
Strangely, I'm getting console messages for all 20 saves in the create controller:
console.log('Saved', item);
But only ONE photo is actually saved. Any ideas why?
Thanks
When you have to perform the same asynchronous task for items in an array, don't use a regular for loop. Check out async.each, it fits better in your scenario, like (just the else part of your code):
var body = JSON.parse(response.body);
async.each(body.data, function (photoData, callback) {
// ------------------------------------------------
// If there is no caption, skip it
//
if (!photoData.caption){
text = '';
}
else{
text = photoData.caption;
}
// ------------------------------------------------
// Create new photo object
//
var photo = new Photo({
link: photoData.link,
username: photoData.user.username,
profilePicture: photoData.user.profile_picture,
imageThumbnail: photoData.images.thumbnail.url,
imageFullsize: photoData.images.standard_resolution.url,
caption: text,
userId: photoData.user.id,
date: photoData.created_time,
_id: photoData.id
});
photo.checkBlacklist(function(err, blacklist){
if (!blacklist){
photo.save(function(err, item){
if (err){
console.log(err);
}
console.log('Saved', item);
callback();
});
}
});
}, function (error) {
if (error) res.json(500, {error: error});
console.log('Photos saved');
return res.json(201, {msg: 'Photos updated'} );
});
Don't forget to install
npm install async
and require async:
var async = require('async');
How about solving it recursively
saveGames = (depth) => {
if (depth > 0) {
var newGame = new Game({ user: user._id });
newGame.save((err, game) => {
if (err) {
console.log(err);
return res.status(203).json({
title: 'error',
error: { message: 'Error Saving Games' }
});
}
user.games.push(game);
user.save((err, user) => {
if (err) {
console.log(err);
return res.status(203).json({
title: 'error',
error: { message: 'Error Saving Games' }
});
}
saveGames(depth - 1);
});
});
}
}
saveGames(5);

cannot get PUT to work on Nodejs / express

I've been writing my first node app, a rest api. I've got everything working pretty good but the PUT won't ever go through. I've tried lots of different combinations, lots of older tutorials and some new ones, and i'm probably pretty close I'm just not sure exactly how to update the record. Also, if you see anything that jumps out at you please feel free to let me know! I'm really liking node so I'd love to learn best practices.
Below is the route to my table, all of the other endpoints are working fine. let me know if you have any questions.
//Database
var mongoose = require("mongoose");
mongoose.connect('mongodb://Shans-MacBook-Pro.local/lantern/');
// Schema
var Schema = mongoose.Schema;
var Person = new Schema({
first_name: String,
last_name: String,
address: {
unit: Number,
address: String,
zipcode: String,
city: String,
region: String,
country: String
},
image: String,
job_title: String,
created_at: { type: Date, default: Date.now },
active_until: { type: Date, default: null },
hourly_wage: Number,
store_id: Number,
employee_number: Number
});
var PersonModel = mongoose.model('Person', Person);
// Return all people
exports.allPeople = function(req, res){
return PersonModel.find(function (err, person) {
if (!err) {
return res.send(person);
} else {
return res.send(err);
}
});
}
// Create A Person
exports.createPerson = function(req, res){
var person = new PersonModel({
first_name: req.body.first_name,
last_name: req.body.last_name,
address: {
unit: req.body.address.unit,
address: req.body.address.address,
zipcode: req.body.address.zipcode,
city: req.body.address.city,
region: req.body.address.region,
country: req.body.address.country
},
image: req.body.image,
job_title: req.body.job_title,
hourly_wage: req.body.hourly_wage,
store_id: req.body.location,
employee_number: req.body.employee_number
});
person.save(function (err) {
if (!err) {
return console.log("created");
} else {
return res.send(err);
}
});
return res.send(person);
}
// Return person by id
exports.personById = function (req, res){
return PersonModel.findById(req.params.id, function (err, person) {
if (!err) {
return res.send(person);
} else {
return res.send(err);
}
});
}
// Delete a person by id
exports.deletePerson = function (req, res){
return PersonModel.findById(req.params.id, function (err, person) {
return person.remove(function (err) {
if (!err) {
console.log("removed");
} else {
return res.send(err);
}
});
});
}
// Update a person by id
exports.updatePerson = function(req, res){
var person = new PersonModel({
first_name: req.body.first_name
});
var upsertData = person.toObject();
console.log(req.params.id); // OK
delete upsertData.id;
person.update({ _id: req.params.id }, upsertData, { multi: false }, function(err) {
if(err) { throw err; }
console.log('updated visit: '+ req.params.id);
res.redirect('/');
});
}
My app.js
// Node Modules
var express = require('express');
var app = express();
app.port = 3000;
// Routes
var people = require('./routes/people');
// Node Configure
app.configure(function(){
app.use(express.bodyParser());
app.use(app.router);
});
// Start the server on port 3000
app.listen(app.port);
/*********
ENDPOINTS
*********/
// People
app.get('/people', people.allPeople); // Return all people
app.get('/people/:id', people.personById); // Return person by id
app.post('/people', people.createPerson); // Create A Person
app.put('/people/:id', people.updatePerson); // Update a person by id, not working
app.delete('/people/:id', people.deletePerson); // Delete a person by id
// Logs
// - Build some logs here for info when the server is going.
// - routes that are loaded, any activity that might be helpful
console.log('Server started on port ' + app.port);
lastly, the json error on put
{
"name": "MongoError",
"err": "Mod on _id not allowed",
"code": 10148,
"n": 0,
"connectionId": 91,
"ok": 1
}
Kept at it and finally figured it out! Here is my PUT function.
app.put('/people/:id', function (req, res) {
var person = new PeopleModel({
first_name: req.body.first_name,
last_name: req.body.last_name,
// etc etc
});
var upsertData = person.toObject();
delete upsertData._id;
return PeopleModel.update({ _id: req.params.id }, upsertData, {upsert: true}, function(err) {
if (!err) {
return res.send("updated");
} else {
console.log(err);
return res.send(404, { error: "Person was not updated." });
}
});
});
I'm going to try and figure out how to auto detect what fields need to be updated, as just for testing i stored one var.
I faced the same problem and find out it was because of res.redirect in put.
Change res.redirect('path') to res.redirect(303, 'path')
In Put and Delete, if you want to redirect to get address, you should pass 303 as first parameter. (source)

Resources