i'm trying to write a "facebook clone" in nodejs with mongoDB.
The problem I'm having is every time a user goes to a user's profile he needs to get his friends array. the way i'm doing it is like this:
router.get("/user/:id/profile", isLoggedIn, (req, res) => {
let friends = [];
User.findById(req.params.id, (err, user) => {
if (err) {
req.flash("error", "There has been an error going to this persons profile.");
res.redirect("back");
} else {
if (user.friends.length > 0) {
for(var i = 0; i < user.friends.length; i++) {
User.findById(user.friends[i], (err, friend) => {
if (err) {
console.log(err);
req.flash("error", "Could not find the friends list")
res.redirect("back")
} else {
friends.unshift({
firstName: friend.firstName,
lastName: friend.lastName,
_id: friend._id
})
console.log(friends)
}
});
}
console.log(friends, "158")
res.render("user", { userData: user, friends: friends })
} else {
// render the page without the friends array.
res.render("user", { userData: user }); // im calling it userData because i have a local template variable called user and i don't want to over-write it
}
}
});
});
and what happens is that where i do the console.log(friends) inside the for loop, i get the friends array with everything correct in it, but when i do the console.log(friends) before the res.render("user") the array is empty. any idea why?
When writing code for node.js you need to get used to async functions as a LOT of addins / classes / code uses this.
Coding is not anymore "linear" but functions are called as soon as previous code is done.
In your case the findById starts a process of finding documents in mongoDB and as soon as it found them it will call the (in your case anonymous) callback function with parameters (err, friend).
Your call of res.render is outside of that callback and will be immediately called after the findById starts. That is to early.
You need to put your res.render within the callback to get the data back.
EDIT: The following code will not run properly because of the for- loop. We need to use promises as shown in Prajvals answer. Please use his second example, as that one works.
User.findById(user.friends[i], (err, friend) => {
if (err) {
console.log(err);
req.flash("error", "Could not find the friends list")
res.redirect("back")
} else {
friends.unshift({
firstName: friend.firstName,
lastName: friend.lastName,
_id: friend._id
})
console.log(friends);
res.render("user", { userData: user, friends: friends })
}
});
It is because of asynchronous nature of node. Using res.render inside your callback function or using promises will sort this out.
Without Promises
User.findById(req.params.id, (err, user) => {
if (err) {
req.flash("error", "There has been an error going to this persons profile.");
res.redirect("back");
} else {
if (user.friends.length > 0) {
for(var i = 0; i < user.friends.length; i++) {
User.findById(user.friends[i], (err, friend) => {
if (err) {
console.log(err);
req.flash("error", "Could not find the friends list")
res.redirect("back")
} else {
friends.unshift({
firstName: friend.firstName,
lastName: friend.lastName,
_id: friend._id
})
console.log(friends)
res.render("user", { userData: user, friends: friends })
}
});
}
console.log(friends, "158")
} else {
// render the page without the friends array.
res.render("user", { userData: user });
}
}
});
With Promise
User.findById(user.friends[i])
.then((friend) => {
friends.unshift({
firstName: friend.firstName,
lastName: friend.lastName,
_id: friend._id
})
})
.then(()=>{
res.render("user", { userData: user, friends: friends })
})
.catch( err=>{
console.log(err);
req.flash("error", "Could not find the friends list")
res.redirect("back")
})
----EDIT----
Now async await can also be used. I have not shown it here though.
The one which i have written without promises will not work.
For doing it with / without promise you need to use recursive way for synchronization instead of for loop.
Related
I would like to improve my skills in Node JS. Right now I'm interested in how to cleanly separate the service and router layers of an application so I can avoid something like code duplication.
The create method of a user scheme shall serve as an example.
In UserService.Js I store the following method for this:
function createUser(req, res, next) {
let user = new User({
userID: req.body.userID,
userName: req.body.userName,
password: req.body.password,
isAdministrator: req.body.isAdministrator
});
user.save().then(function() {
res.send(user);
}).catch(err => {
console.log(err);
});
}
The following code is stored in UserRouter.Js:
router.post('/publicUser', userService.createUser)
The code works, but the separation of concerns is not respected. How do I write the create function now with a callback function ?
My attempt looks like this:
UserService.js
function createUser() {
let user = new User
return user;
}
UserRoute.js
router.post('/publicUser',function(req,res,next){
let newOne=userService.createUser()
newOne.userID=req.body.userID
newOne.userName=req.body.userName
newOne.password=req.body.password
newOne.isAdministrator=req.body.isAdministrator
newOne.save().then(function() {
res.send(newOne);
}).catch(err => {
console.log(err);
})})
It works. But does a more elegant way exist ?
Below is the code to give you an idea of implementation. You can further enhance as per the complexity and requirements.
UserRouter.Js
// import service here
router.post('/publicUser', createUser)
async function createUser(req, res, next) {
try {
const response = await UserService.createUser(req.body);
res.send(response); // Enhance 'res' object here and return as per your requirement.
} catch (error) {
// log error
res.status(500).send(''); // Enhance 'res' object here with error and statuscode and return as per your requirement.
}
}
UserService.Js
async function createUser(body) {
// check emptiness if any for body and throw proper errors.
let userModelData = UserModel.getUserInsertPayload(body);
return UserRepository.save(userModelData);
}
UserRepository.js
// all code related to persistance. This is separate layer.
async function save(user) {
// Do any enhancement you need over the payload.
return User.save(user);
}
UserModel.js
// import User here. Create a payload related to User specific requirements.
function getUserInsertPayload(body) {
return new User({
userID: req.body.userID,
userName: req.body.userName,
password: req.body.password,
isAdministrator: req.body.isAdministrator
});
}
I have the code below. Its a standard blog type of setup with users which have posts and comments. Comments are the child of both users and post . Posts belong just to users. Im having a problem posting to comments table. IM not getting any errors when using the insert function , however, when I post a comment to the database nothing gets saved to the comments table . If i do a request to retrieve the comments table , the table still shows empty. What am i doing wrong here .
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
db("users")
.where({ id: Number(userID)})
.then((user) => {
db('posts') .where({ id: Number(postID)})
.then((post) => {
//verify if post and user exists
if (post && user) {
req.body.content ? insertComment({
content: req.body.content,
user: userID,
post: postID
})
.then(
res.status(201).json(req.body)
)
.catch((err) => {
console.log(err);
})
: res.status(400).json({
errorMessage: "Please insert text .",
});
} else {
res.status(404).json({
message: "user not found",
});
}
})
})
.catch((err) => {
res.status(500).json({
err,
message: "Error processing request",
});
});
});
function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post
});
}
since you're already using async function i'd first recommend to use async/await, second notice is that knex returns an array and not an object for example
db("users")
.where({ id: Number(userID)})
.then((user) => {
// user is an array
});
you can chain a query with .first() to retrieve the first object and not an array
Reference from knex documentation
using async/await could save you from callback hell
server.post("/users/:id/posts/:id2/comments", async (req, res) => {
const userID = req.params.id;
const postID = req.params.id2;
try {
const user = await db("users").where("id", Number(userID)).first();
const post = await db("posts").where("id", Number(postID)).first();
if (post && user) {
if (req.body.content) {
await insertComment({
content: req.body.content,
user: userID,
post: postID,
});
return res.status(201).json(req.body);
} else {
return res.status(400).json({
errorMessage: "Please insert text .",
});
}
} else {
return res.status(404).json({
message: "user or post not found",
});
}
} catch (err) {
return res.status(500).json({
err,
message: "Error processing request",
});
}
});
async function insertComment(comment) {
return db("comments").insert(comment).where({
user: comment.user,
post: comment.post,
});
}
and if you have lots of relationships in your application you might find it useful if you want to use an ORM like Objection as it is built on knex.
I'm using SailsJS as an API with Waterline connected to a MongoDB. I'm trying to put together an endpoint to edit existing DB entries but can't seem to get it to work and I'm hitting a wall as to why.
My route:
'post /edit/safety/:id': {
controller: 'SafetyController',
action: 'editSafety'
},
My controller function:
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOneById(id).then((err, safety) => {
if (err) {
res.send(500, err);
return;
}
if (!safety) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
safety.save((err, updatedSafety) => {
if (err) {
re.send(500, err);
return;
}
res.send(200, updatedSafety);
});
});
},
Any push in the right direction would be greatly appreciated.
I don't recognize the Safety.findOneById method - is this something you have custom built? If not, then it is likely your problem.
Try swapping it for either:
Safety.findOne(id)
or
Safety.findOne({id: id})
Note that the returned object will be a model instance if the record exists, and undefined otherwise. If you decide to go with Safety.find instead then the returned value will be an array containing all models matching the query.
Looks like the main issue was transposing the response and err objects. It was successfully completing the query, but loading it into the err object which gets caught and a 500 error is thrown. So I changed that and simplified in a few other places.
editSafety: function editSafety(req, res) {
var id = req.params.id;
Safety.findOne(id).then((response, err) => {
var safety = response;
if (err) {
res.send(500, err);
return;
}
if (!response) {
res.send(404, err);
return;
}
safety.title = req.body.title;
safety.description = req.body.description;
safety.status = req.body.status;
Safety.update({
id: id
}, safety)
.then((result) => {
res.json(200, 'Ok!');
})
.catch((err) => {
sails.log.error('SafetyController.editSafety', err);
})
});
},
I get user query information when use req.user at first time but in second inside function I got cannot read property user of null.
router.post("/orders", function (req, res) {
console.log(req.user);//here I can see user info!
orders.count({
customerInfo: req.user//here I can get user info!
}, function (req, count) {
if (count > 0) {
console.log(count);
orders.findOne({
customerInfo: req.user//here:cannot read property user of null
}, function (err, orders) {
products.findOne({
_id: req.body.id
}, function (err, products) {
if (err) {
console.log(err);
} else {
orders.productInfo.push(products);
orders.save(function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
}
});
});
}
You have req in the inner function and it's overriding the outter req with the user. You can create a var outside with the user:
Example:
post("/orders", function (req, res) {
const user = req.user;
orders.count({ customerInfo: user//here I can get user info!
}, function (req, count) {
//Here you can use user
});
});
In my opinion your code is what we call callback hell, so it's advisable to refactor It using control flow, you can use promises, generator functions and other artifacts for this cases.
I have a simple query in mongoose:
var emails = [];
User.find({ subscribed: true })
.exec(function(err, users) {
console.log(users)
if (err) {
console.log(err)
}
async.forEach(users, function(user, callback) {
if (emails.indexOf(user.email) == -1) {
console.log(user.email)
emails.push(user.email)
send(post, user.email)
callback()
}
})
})
There are 3 Users. When I run this query, only one is ever returned and it's always the same User. When I replace the line User.find({ subscribed: true }) with User.find({}), all accounts are returned, however when the User are printed in the console they all clearly show subscribed: true.
Additionally, the query executes as expected on my local development environment. The issue only occurs on my production server. Could this be a mongo/mongoose/Node version issue?
Try this:
var emails = [];
User.find({ subscribed: true },(function(err, users) {
console.log(users)
if (err) {
console.log(err)
}
async.forEach(users, function(user, callback) {
if (emails.indexOf(user.email) == -1) {
console.log(user.email)
emails.push(user.email)
send(post, user.email)
callback()
}
})
})