Mongoose - trying to do 'JOINS' in MEAN stack - node.js

I am having a hard time understanding the async nature of NodeJS.
So, I have an articles object with this schema:
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
creator: {
type: Schema.ObjectId,
ref: 'User'
}
});
and the User schema is:
var UserSchema = new Schema({
firstName: String,
lastName: String,
...
});
The problem is when I query for all the documents like so:
exports.list = function(req, res) {
// Use the model 'find' method to get a list of articles
Article.find().sort('-created').populate('creator', 'firstName lastName fullName').exec(function(err, articles) {
if (err) {
// If an error occurs send the error message
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
// Send a JSON representation of the article
res.json(articles);
}
});
};
I get all the articles back successfully, but for some reasons, the article creator is returning different results
for locally authenticated users (localStrategy) and facebook authenticated users (facebook strategy) for locally authenticated users, I get:
articles = {
creator: {
id: 123,
firstName: 'Jason',
lastName: 'Dinh'
},
...
}
for fb authenticated users, I get:
articles = {
creator: {
id: 123
},
...
}
I can't seem to get a grip on PassportJS API, so what I want to do is
iterate through articles and for each article, find the user document using the article creator ID and add the user firstName and lastName to the articles object:
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
});
}
res.json(articles);
You can probably already see the problem here... my loop finishes before the documents are returned.
Now, I know that MongoDB doesn't have any 'joins' and what I want to do is essentially return a query that 'joins' two collections. I think I'm running into problems because I don't fundamentally understand the async nature of
node.
Any help?

You can use find instead of findOne and iterate inside your callback function.
User.find({ }, function(err, personList){
for each person in personList {
for each article in articles {
if (person._id === article.creator._id) {
//add user firstName and lastName to article
}
}
}
res.json(articles);
});
UPDATE:
Considering the scenario that #roco-ctz proposed (10M users), you could set a count variable and wait for it to be equal to articles.length:
var count = 0;
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
count += 1;
});
}
while (count < articles.length) {
continue;
}
res.json(articles);

Related

Insert same records multiple times to the Mongo database with NodeJs

I want to achive funcionality, where user in the frontend writes how many posts he want to insert in the database.. He can write 1, 5, 10, 15,.. up to 50 same posts.
The posts are then the SAME in the database, just this manually generated _id is different.
At first I thought that it can be done just like that:
exports.addPost = async (req: any, res: any) => {
try {
const newPost = new Post({
title: req.body.title,
description: req.body.description,
author: req.body.author,
});
for (let i: number = 0; i < 5; i++) {
await newPost .save();
}
res.status(201).json(newContainer);
} catch (err) {
res.status(400).json(err);
}
};
Post schema:
const PostSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
author: {
type: Schema.Authors.ObjectId,
ref: "Authors",
required: true,
},
});
module.exports = mongoose.model("Posts", PostSchema);
but I am not sure, if this is really the way to go.. What is some good practice for this (assuming that the number 5 in for loop will come in req.body.. So from user input.
Thanks
You can just use the following code:
try {
await Post.create(
new Array(5).fill(true).map((_) => ({
title: req.body.title,
description: req.body.description,
author: req.body.author,
}))
);
res.status(201).json(newContainer);
} catch (err) {
res.status(400).json(err);
}
model.create does accept passing an array of (new) documents to it. By mapping a new array of size 5 (or depending on user input) to your custom document and passing it to the create function will result in multiple documents created. A huge benefit is that you only have to perform one single database call (and await it).

How to count unique views to a blog post (logged in and non logged in user) using Node.Js, MongoDB, Mongoose, Express

I want to display how many times a blog post has been read kind of like what Business Insider has.
The objective is...
View count that doesn't increment with every reload.
Every blog post stores its own view count
Fetch the view count from MongoDB/Mongoose Schema field and display it in HTML.
var express = require('express');
var cookieParser = require('cookie-parser');
var mongoose = require('mongoose');
var express-session = require('express-session');
//Show Blog Post
router.get('/blog/:categorySlug/:slug', function (req, res)
var slug = req.params.slug;
Blog.findOne({'slug' : slug}).populate('comments').exec(function (err, foundBlog) {
if (err) {
console.log(err);
} else {
res.render('show', { main: foundBlog, title: foundBlog.title});
}
});
});
I know that if i use req.session.views, it will increment the view count with every reload across all pages.
router.get('/blog/:categorySlug/:slug', function (req, res) {
req.session.views = (req.session.views || 0) + 1
var slug = req.params.slug;
Blog.findOne({'slug' : slug}).populate('comments').exec(function (err, foundBlog) {
if (err) {
console.log(err);
} else {
res.render('show', { main: foundBlog, title: foundBlog.title, pageViewCount: req.session.views});
}
});
});
So what could i do to save the view count of each blog separately and store that view count value in that blog post's viewCount field in the database and then render it in HTML using <%= main.viewCount %>
// Blog Schema
var mainBlogSchema = new mongoose.Schema({
image: String,
imageDescription: String,
priority: {
type: String,
default: ""
},
title: String,
content: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
username: String,
name: String,
},
slug: {
type: String,
unique: true,
},
status: String,
viewCount: {
type: Number,
default: 0,
},
category: String,
categorySlug: String,
tags: String,
updated: {
type: Boolean,
default: false
},
date: { type: Date, default: Date.now },
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
},{
timestamps: {
createdAt: 'createdAt',
updatedAt: 'updatedAt'
}
});
One approach can be (might need changes)
Before the UI page loads, a script checks for user-token(separate token or some random string token)in the browser.
If token is not available then send request for new token along with page-id.
If token is available, call API with the token and page-id.
Make sure it gets called for every page load or even page transition.
Backend implementation can be like
Page collection will have page-id along with visits:[] field.
One API creates token and stores it in a separate collection(users) and follows step 3.
Another API will take the token and page-id as input, it will first. check the token is available in our collection(user) and if so it will do a mongo update on Page collection with
db.pages.update(
{ _id: page-id },
{ $addToSet: {visits: [ user_token ] } }
)
This query takes care if the token stored is unique no need to worry about anything. We can maintain a separate field called visitCount if needed, which gets updated with the latest count once query gets executed updating at least one record.
Pros
Page refresh will not affect the count.
The token is also maintained at our end so we can validate.
Cons
We will need huge space to store these token and we have to delete them if a user did not visit the site for a very long time.
API calls overhead.

Nested query with mongoose

I have three models: User, Post and Comment
var User = new Schema({
name: String,
email: String,
password: String // obviously encrypted
});
var Post = new Schema({
title: String,
author: { type: Schema.ObjectId, ref: 'User' }
});
var Comment = new Schema({
text: String,
post: { type: Schema.ObjectId, ref: 'Post' },
author: { type: Schema.ObjectId, ref: 'User' }
});
I need to get all posts in which the user has commented.
I know it should be a very simple and common use case, but right now I can't figure a way to make the query without multiple calls and manually iterating the results.
I've been thinking of adding a comments field to the Post schema (which I'd prefer to avoid) and make something like:
Post.find()
.populate({ path: 'comments', match: { author: user } })
.exec(function (err, posts) {
console.log(posts);
});
Any clues without modifying my original schemas?
Thanks
You have basically a couple of approaches to solving this.
1) Without populating. This uses promises with multiple calls. First query the Comment model for the particular user, then in the callback returned use the post ids in the comments to get the posts. You can use the promises like this:
var promise = Comment.find({ "author": userId }).select("post").exec();
promise.then(function (comments) {
var postIds = comments.map(function (c) {
return c.post;
});
return Post.find({ "_id": { "$in": postIds }).exec();
}).then(function (posts) {
// do something with the posts here
console.log(posts);
}).then(null, function (err) {
// handle error here
});
2) Using populate. Query the Comment model for a particular user using the given userId, select just the post field you want and populate it:
var query = Comment.find({ "author": userId });
query.select("post").populate("post");
query.exec(function(err, results){
console.log(results);
var posts = results.map(function (r) { return r.post; });
console.log(posts);
});

Mongoose - can't save a document's properties

So I have users and teams. I would like teams to have an array of users and I add a user to the team using the addUser function. I push to the teams user array successfully and save the results but right after that function I go to print out the same team and there are no users in the array. I am not sure if this is a .save() problem or what.
Any help is appreciated, thank you.
...
var userSchema = new Schema({
name: { type: String, required: true, unique: true },
age: Number
});
var teamSchema = new Schema({
name: { type: String, required: true, unique: true },
user: [{ type: Schema.ObjectId, ref: 'User' }]
});
userSchema.statics.createUser = function(opts, cb) {...};
teamSchema.statics.createTeam = function(opts, cb) {...};
teamSchema.statics.addUser = function(opts, cb) {
Team.find({_id: opts.team}).exec( function (err, team) {
team[0].user.push(opts.user);
team[0].save(function(err, save) {
console.log("--------------------");
console.log(team[0]); //team contains the added user here
console.log("--------------------");
return cb(null);
});
});
};
var User = mongoose.model('User', userSchema);
var Team = mongoose.model('Team', teamSchema);
var async = require('async');
var user1;
var user2;
var team;
async.waterfall([
function(d){User.createUser({name :'test1'},function(err, user){
user1 = user;
d(err);
});
},
function(d){User.createUser({name :'test2',age :20},function(err, user){
user2 = user;
d(err);
});
},
function(d){Team.createTeam({name :'team'},function(err, obj){
team = obj;
d(err);
});
},
function(d){Team.addUser({user : user1._id,team : team._id}, function(err){
console.log(team);
d(err);
});
},
function(d){Team.addUser({user : user2._id,team : team._id}, function(err){
console.log(team);
d(err);
});
}
],function(err){
if(err){
console.log(err);
}
else{
User.count({},function(err,count){console.log("Number of users:", count);});
Team.count({},function(err,count){console.log("Number of teams:", count);});
console.log(team);
}
});
returns:
--------------------
{ _id: 5583ed760958ab941a58bae9,
name: 'team',
__v: 1,
user: [ 5583ed760958ab941a58bae7 ] } //user1 added
--------------------
{ __v: 0, name: 'team', _id: 5583ed760958ab941a58bae9, user: [] }
//after that function call the team has no users
--------------------
{ _id: 5583ed760958ab941a58bae9,
name: 'team',
__v: 2,
user: [ 5583ed760958ab941a58bae7, 5583ed760958ab941a58bae8 ] }
//user2 added and user1 still there
--------------------
{ __v: 0, name: 'team', _id: 5583ed760958ab941a58bae9, user: [] } //no users again
Number of users: 2
Number of teams: 1 //only 1 team exists
Perhaps try using an update and push which would also simplify your code?
findAndModify (mongodb command)
findOneAndUpdate (mongoose command)
Team.findOneAndUpdate({"_id" : opts.team} , {$addToSet : { "user" : opts.user}}, function (err, foundUpdatedTeam) {
return cb(err,foundUpdatedTeam);
}, {new : true});
// be sure to update your team variable to reflect what is also in the database.
something like this:
function(d){Team.addUser({user : user1._id,team : team._id}, function(err, updatedTeam)
{
team = updatedTeam;
console.log(team);
d(err);
});
Few Notes:
I would change the team schema from user to TeamUsers or something more descriptive..it's a little confusing now.
note the use of AddToSet, that ensures that if you already added the user you won't be adding him twice.
This code is shorter and cleaner, you don't need to find the object if you can use the dedicated function, if however you wish to add the user to multiple teams I'd use update which allows you to update multiple documents.

MeanJS MongoDb insert issue

I'm creating an application using MEANJS. I've a mongoose schema defined like this:
var UserdetailSchema = new Schema({
fullName: {
type: String,
trim: true
},
userName: {
type: String,
trim: true
},
mobile: {
type: String,
default: '',
trim: true
},
address: {
type: String,
trim: true
},
suburb: {
type: String
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Userdetail', UserdetailSchema);
What I'm trying to achieve is after login user is redirected to edit view to update rest of the info like "mobile, suburb, address" etc which is in userdetails schema.
This is my controller. I've changed default create() method to the following: I'm redirecting to the edit it as soon as the first step of inserting is complete.
// Create new Userdetail for current user
function create(FullName,UserName) {
// Create new Userdetail object
var userdetail = new Userdetails ({
fullName: FullName,
userName: UserName
});
// Redirect after save
userdetail.$save(function(response) {
$location.path('userdetails/' + response._id+'/edit');
console.log(response._id);
// Clear form fields
//$scope.name = '';
}, function(errorResponse) {
console.log(errorResponse.data.message);
$scope.error = errorResponse.data.message;
});
};
To create a user details I'm only inserting fullName, and userName as a first step and updating it later.
issue is, it is only allowing me 1 userdetails to insert and if I try to insert another userdetails of another user. it gives an error "Name already exists", though there is no name in the schema.
Server side code to create userdetails
/**
* Create a Userdetail
*/
exports.create = function(req, res) {
var userdetail = new Userdetail(req.body);
userdetail.user = req.user;
userdetail.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(userdetail);
}
});
};
I got it working after droping my collection "userdetails" from shell and trying inserting again. I followed this link Mongodb- Add unique index on an existing collection . It was more of MongoDB issue.

Resources