I'm trying to link between two mongoose collections. So that user's information are stored in a collection named user, and his content is stored in another collection.
So far I copied some code from here and edited it;
then with Postman, I created a username named "example";
then I created a post with a random content, and as author I set it "example", but it seems that it doesn't work, when I get '/test' it logs :
Populated User { posts: [], _id: 5c530cd4ede117109cf1a5e9,
username: 'example', __v: 0 }
posts is empty as you see, what should I change in order to fix that?
const UserSchema = new mongoose.Schema({
username: String,
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
})
const PostSchema = new mongoose.Schema({
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
function getUserWithPosts(username){
return User.findOne({ username: username })
.populate('posts').exec((err, posts) => {
console.log("Populated User " + posts);
})
}
const Post = mongoose.model('Post', PostSchema, 'posts');
const User = mongoose.model('User', UserSchema, 'users');
app.post('/testUser', (req, res) => {
var username = req.body.username;
var clientModel = new User({
username: username,
});
clientModel.save(function (error) {
if (error) {
console.log(error);
}
res.send({
success: true,
username: username,
message: 'account saved successfully!'
});
});
});
app.post('/testPost', (req, res) => {
var content = req.body.content;
var author = req.body.author;
var clientModel = new Post({
content: content,
author: author
});
clientModel.save(function (error) {
if (error) {
console.log(error);
}
res.send({
success: true,
content: content,
author: author,
message: 'account saved successfully!'
});
});
});
app.get('/test', (req, res) => {
res.send({
posts: getUserWithPosts("example")
})
});
EDIT
I see whats going on here now. It appears you have an issue of circular references. Your user.posts references the posts that the user created. The posts.author references the author who created the post. If you want to make this work, you will have to go in and update the user object, after you create the post object, and pass in the post._id.
You can do one other, better thing. You can use virtuals. Mongoose can calculate values run time that reference other collections. This would be my suggestion. The downside is, virtuals are not stored in the database, and you have to explicitly populate this field every time you want it. Heres an example:
User Model
const UserSchema = new mongoose.Schema({
username: String
})
UserSchema.virtual('posts', {
ref: 'Post',
localField: 'username',
foreignField: 'author',
justOne: false
});
This will create the virtual, and whenever you find a user and call .populate('posts'), you will get a array of posts back which the user has authored.
Read more about mongoose virtuals here: http://thecodebarbarian.com/mongoose-virtual-populate
Related
Its been a tough time for me trying to figure this out.
The problem I'm trying to solve is this:
I have USERS who has list of COMPANIES,
this COMPNAY has list of PROJECTS etc.
I'm trying to create a PROJECT under a COMPANY and a COMPANY under a USER using referencing.
This USER is going to be populated from form fields likewise COMPANY PROJECT
USERS SCHEMA
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//============= User Schema============//
const userSchema = new Schema({
name: String,
email: String,
//referencing user's company/companies
company: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'company',
},
],
},{
timestamps: true
});
const Users = mongoose.model('user', userSchema);
module.exports = Users;
COMPANY SCHEMA
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//======== Company schema ========//
const compSchema = new Schema({
company_name: String,
//referencing user schema
user: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
],
//referencing projects
projects: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'project',
},
],
},{
timestamps: true
});
const Company = mongoose.model('company', compSchema);
module.exports = Company;
PROJECT SCHEMA
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//PROJECT SCHEMA
const projectSchema = new Schema({
project_title: String,
//referencing company schema
company: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'company',
}
],
},{
timestamps: true
});
const Projects = mongoose.model('project', projectSchema);
module.exports = Projects;
CONTROLLERS
const Users = require('./userSchema');
const Company = require('./companySchema');
const Projects = require('./projectSchema');
//create user
exports.createUser = async (req, res) => {
const data = {
name: req.body.name,
email: req.body.email,
}
Users.create(data, (err, done) => {
if(err) return err;
res.json({UserData: done});
return;
});
}
exports.getAllUsers = async(req, res) => {
}
//create company
exports.createCompany = async (req, res) => {
const CompanyData = {
company_name: req.body.company_name,
}
Company.create(companyData, (err, done) => {
if(err) return err;
res.json({CompanyDetails: done});
return;
});
}
exports.getllCompany = async (req, res) => {
}
//create project
exports.createProject = async (req, res) => {
const projectData = {
project_title: req.body.project_title,
}
Projects.create(projectData, (err, done) => {
if(err) return err;
res.json({ProjectDetails: done});
return;
});
}
exports.getAllProjects = async (req, res) => {
}
I understand that populate can be used to push documents to children. Any assistance offered will be highly appreciated
Store reference
Firstly, while creating a Company inside createCompany controller, you should add user _id in users array like this:
const CompanyData = {
company_name: req.body.company_name,
users: [req.body.userId] // Lets assume, user _id is coming in request body
}
Company.create(companyData, (err, done) => {
// Your code
});
Then, when you create a Project, add _id of Company you just created, in createProject controller like this:
const projectData = {
project_title: req.body.project_title,
company: [req.body.comapnyId] // Again assuming, company _id is coming in request body
}
Projects.create(projectData, (err, done) => {
// Your code
});
Update reference
Also, as I see in your schemas, you are trying to keep it bidirectional, like user's _ids in company doc & company's _ids in user doc.
To achieve this, if you create a user, you should update the corresponding company with pushing(addToSet actually) new user _id in user array.
Example:
To create a user in controller, run two scripts synchronously one-by-one:
Create a user with company _id and other details.
const data = {
name: req.body.name,
email: req.body.email,
company: [req.body.companyId]
}
Users.create(data, (err, done) => {
// Your code
});
Update company doc(whose _id is saved in above user) with _id of newly created user.
const updateCompany = { "$addToSet": { user: user._id } }; // Get `user._id` from above synchronous script
Company.update({_id: req.body.companyId}, data, (err, done) => { // Updated filter
// Your code
});
I'm trying to design a Twitter style API. I've come up with the following Schema:
User collection
username
password
Followers collection
follower_id
followee_id
start_date
end_date
Eg:
{
follower_id: 1235
followee_id: 1024
start_date: November 3, 2018
}
which means user 1024 follows user 1235.
Absence of end_date indicates that the user is still following.
Tweets collection
text
date_created
user_id
user_id will be a reference to User collection.
How can I achieve follow/unfollow action with above schema?
For the user collection, you could have a following[] array. As in, you put each user id that they are following in the array and then remove it when they unfollow a person. Then with express, you can handle a post request sent by the front end with express. Of course you would need a boolean to say like:
// To show the button saying "follow" or "unfollow"
var followingArray = [123414, 312456, 656465];
var following = false;
if (followingArray.indexOf(userid)) {
following = true;
} else {
following = false;
}
Obviously, this is just a concept and you would need other code from mongoose or the regular mongodb module to get the user id. This would also decide whether or not the post request link is going to be http://myapp.com/follow or http://myapp.com/unfollow. Based on these two post requests, you would handle the data like the user id with body-parser and express:
var bodyParser = require('body-parser');
const express = require('express');
const app = express();
const port = 8080
app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
app.post('/follow', (req, res) => {
//handle data here for a request to follow someone
var userid = req.body.userid;
})
app.post('/unfollow', (req, res) => {
//handle data here for a request to unfollow someone
var userid = req.body.userid
})
app.listen(port, () => {
console.log(`Server running on ${port}`);
})
Then you would use this data with mongoose or the mongodb module to remove or add that user id from the array and change the following boolean to true or false. Then if you wanted to add other features like a followers list or a following list, you could use the user id to do so. I am not giving exact code on how to do this as there are many concepts involved. I am just trying to give you the concepts that you would use in doing so.
I recommend this:
Keep the database as "flat" as possible. That means fewer collections.
You'd probably need only 2 collections: users and tweets
In your users schema should look something like this:
// Asuming you use mongoose, if you haven't heard about it, you should look it up.
const userSchema = new Schema({
username: {type: String, required: true, trim: true},
password: {type: String, required: true},
following: {type: Array}, // Store all user id of the people that this user is following in an array.
followers: {type: Array}, // Store all users id of this user's followers in an array.
tweets: {
type: Schema.Types.ObjectId, // Store all tweets by this user in an object.
ref: "Tweet" // Link to Tweet Schema
}
}, {_id: true});
Your tweets schema:
const tweetSchema = new Schema({
text: {type: String, required: true}
// You can also have a userId to store the id of the user whose this tweet belongs to
}, {_id: true});
You then can use create to create users or tweets, update to edit following or followers.
Obviously there are several ways to achieve what you want. This is just one of them.
Following Solution Works for me:
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
},
following: {
type: Array
},
followers: {
type: Array
},
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
module.exports = User;
router.patch("/api/v1/follow/", auth, async (req, res) => {
try {
let whomFollowed = await User.findByIdAndUpdate({ _id: req.body.followingId},
{ $push: { following: req.body.followerId } }
);
let whoFollowedMe = await User.findByIdAndUpdate({ _id: req.body.followerId },
{ $push: { followers: req.body.followingId } }
)
return res.status(200).send({ message: "User Follow Success"});
} catch (e) {
return res.status(500).send({ message: "User Follow Failed", data: e.message });
}
});
router.patch("/api/v1/unfollow/", auth, async (req, res) => {
try {
let whomUnFollowed = await User.findByIdAndUpdate({ _id: req.body.followingId },
{ $pull: { following: req.body.followerId } }
);
let whoUnFollowedMe = await User.findByIdAndUpdate({ _id: req.body.followerId },
{ $pull: { followers: req.body.followingId } }
)
return res.status(200).send({ message: "User UnFollow Success"});
} catch (e) {
return res.status(500).send({ message: "User UnFollow Failed", data: e.message });
}
});
My goal is to build a simple news feed in node.js with the help of mongodb and redis. It similar like twitter
So the scenario is pretty straight forward, once User A follow User B. Later on User's A News feed (Home page) will be shown User B's Activity like what he posted.
Schema for User
const UserSchema = new Schema({
email: { type: String, unique: true, lowercase: true},
});
const followSchema = new Schema(
{
user: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
target: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
});
Currently the design of my user's schema is pretty simple, when I follow another user, I will just create the Follow Schema Object
and there is another schema, which is post schema
/* This is similar like the Tweet */
var PostSchema = new Schema({
// Own by the user
creator: { type: Schema.Types.ObjectId, ref: 'User' }
body: String,
});
This schema is for user to post anything, similar like twitter posting.
Let say I have followed bunch of users
{
user: 'me',
target: 'draco'
},
{
user: 'me',
target: 'donald'
},
{
user: 'me',
target: 'joker'
}
and let say one of my followers, post something. How do i present it to my current news feed?
/* I'm following Joker */
app.post('/follow', (req, res, next) => {
let follow = new Follow();
follow.user = "me";
follow.target = "joker";
// Do i need to use redis to subscribe to him?
follow.save();
})
/* Joker posted something */
app.post('/tweet',(req, res, next) => {
let post = new Post();
post.creator = "joker";
post.body = "Hello my name is joker"
post.save();
// Do i need to publish it using redis so that I will get his activity?
});
Here's my attempt
app.get('/feed', function(req, res, next) {
// Where is the redis part?
User.findOne({ _id: req.user._id }, function(err, foundUser) {
// this is pretty much my attempt :(
})
})
When should I use redis to actually do the pub and sub? so that I could take the content of one of my followers and show it on my timeline?
I have built a social network which has a news feed, too. Here is how I did it.
Basically, you have 2 methods to built a newsfeed:
Fanout on write (push) method
Fanout on read (pull) method
Fanout on write
First, you will need another collection:
const Newsfeed = new mongoose.model('newsfeed', {
owner: {type: mongoose.Types.ObjectId, required: true},
post: {type: mongoose.Types.ObjectId, required: true}
});
When a user post something:
Get n follower
Push (fanout) this post to n follower
When a user get a feed:
Get from Newsfeed collection
Example:
router.post('/tweet', async (req, res, next) => {
let post = await Post.create({});
let follows = await Follow.find({target: req.user.id}).exec();
let newFeeds = follows.map(follow => {
return {
user: follow.user,
post: post.id
}
});
await Newsfeed.insertMany(newFeeds);
});
router.get('/feed', async (req, res, next) => {
let feeds = await Newsfeed.find({user: req.user.id}).exec();
});
Fanout on read
When a user post something:
Save
When a user get feed
Get n following
Get posts from n following
Example
router.post('/tweet', async (req, res, next) {
await Post.save({});
});
router.get('/feeds', async (req, res, next) {
let follows = await Follow.find({user: req.user.id}.exec();
let followings = follows.map(follow => follow.target);
let feeds = await Post.find({user: followings}).exec();
});
You don't need Redis or pub/sub to implement a newsfeed. However, in order to improve the performance, you may need Redis to implement some kind of cache for this.
For more information or advance technique, you may want to take a look at this.
User Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const userSchema = new Schema({
name:{type:String},
email: { type: String, unique: true, lowercase: true},
},{
collection: 'User'
});
var User = module.exports = mongoose.model('User', userSchema);
Follow Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var followSchema = new Schema(
{
follow_id: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
leader_id: { type: Schema.Types.ObjectId, required: true, ref: 'User' }
},{
collection:'Follow'
});
var Follow = module.exports = mongoose.model('Follow', followSchema);
Post Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var postSchema = new Schema({
creator: { type: Schema.Types.ObjectId, ref: 'User' }
body: {type: String , required:true},
created_at :{type:Date , default:Date.now}
},{
collection:'Post'
});
var Post = module.exports = mongoose.model('Post', postSchema);
Now Suppose you have 3 users in User collection :
{ _id: ObjectID('5a2ac68d1413751391111111') ,name:'John' , email:'john#gmail.com'}
{ _id: ObjectID('5a2ac68d1413751392222222') ,name:'Morgan' , email:'morgan#yahoo.com'}
{ _id: ObjectID('5a2ac68d1413751393333333') ,name:'Emily' , email:'emily#outlook.com'}
Now John Follows Morgan and Emily :
so in Follow collection there are two records
1) follow_id = John's ID and leader_id = Morgan's ID
2) follow_id = John's ID and leader_id = Emily's ID
{
_id: ObjectID('5a2ac68d141375139999999'),
follow_id : ObjectID('5a2ac68d1413751391111111'),
leader_id : ObjectID('5a2ac68d1413751392222222')
},
{
_id: ObjectID('5a2ac68d1413751393333333'),
follow_id : ObjectID('5a2ac68d1413751391111111'),
leader_id : ObjectID('5a2ac68d1413751393333333')
}
Now if you want to get User's Following :
app.get('/following/:user_id',function(req,res){
var userid=req.params.user_id;
Follow.find({follow_id:mongoose.mongo.ObjectID(userid)})
.populate('leader_id')
.exec(function(err,followings){
if(!err && followings){
return res.json({followings:followings});
}
});
});
for getting User's Followers :
app.get('/followers/:user_id',function(req,res){
var userid=req.params.user_id;
Follow.find({leader_id:mongoose.mongo.ObjectID(userid)})
.populate('follow_id')
.exec(function(err,followers){
if(!err && followers){
return res.json({followers:followers});
}
});
});
npm install redis
in your app.js :
var redis = require('redis');
var client = redis.createClient();
When one user create post :
app.post('/create_post',function(req,res){
var creator=new mongoose.mongo.ObjectID(req.body.creator);
var postbody=req.body.body;
async.waterfall([
function(callback){
// find followers of post creator
Follow.find({leader_id:creator})
.select({ "follow_id": 1,"leader_id":0,"_id": 0})
.exec(function(err,followers){
if(!err && followers){
callback(null,followers);
}
});
},
function(followers, callback){
// saving the post
var post=new Post({
creator: creator,
body: postbody
});
post.save(function(err,post){
if(!err && post){
// adding newly created post id to redis by key userid , value is postid
for(var i=0;i<followers.length;i++){
client.sadd([followers[i].follow_id,post.id]);
}
callback(null,post);
}
});
}
], function (err, result) {
if(!err && result){
return res.json({status:"success",message:"POST created"});
}
});
});
Now For Getting User NewsFeed :
1) first get array of postid from redis key of userid
2) loop through postid and get post from mongo
Function for get newsfeed by userid :
app.get('/newsfeed/:user_id',function(req,res){
var userid=req.params.user_id;
client.smembers(userid,function(err, reply) {
if(!err && reply){
console.log(reply);
if(reply.length>0){
var posts=[];
for(var i=0;i<reply.length;i++){
Post.findOne({_id:new mongoose.mongo.ObjectID(reply[i])}).populate('creator').exec(function(err,post){
posts.push(post);
});
}
return res.json({newsfeed:posts});
}else{
// No News Available in NewsFeed
}
}
});
});
Here we have use redis to store [userid,array of postids] for newsfeed ,
but if you dont want to use redis than just use below Newsfeed Model and store user_id and post_id for newly created post and then display it.
NewsFeed Schema :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var newsFeedSchema = new Schema({
user_id: {type: Schema.Types.ObjectId, refer:'User' , required:true}
post_id: {type: Schema.Types.ObjectId, refer:'Post' , required:true},
},{
collection:'NewsFeed'
});
var NewsFeed = module.exports = mongoose.model('NewsFeed', newsFeedSchema);
Helpful link for Redis : https://www.sitepoint.com/using-redis-node-js/
for Async : https://caolan.github.io/async/docs.html#
This is What I want as a final result. I have no idea how to update array of indexes.
my Schema is built using mongoose
var postSchema = new Schema({
title: {type:String},
content: {type:String},
user:{type:Schema.ObjectId},
commentId:[{type:Schema.ObjectId, ref:'Comment'}],
created:{type:Date, default:Date.now}
});
var commentSchema = new Schema({
content: {type:String},
user: {type:Schema.ObjectId},
post: {type:Schema.ObjectId, ref:'Post'}
created:{type:Date, default:Date.now}
});
My controllers are:
// api/posts/
exports.postPosts = function(req,res){
var post = new Post({
title: req.body.title,
content: req.body.content,
user: req.user._id
});
post.save(function(err){
if(err){res.send(err);}
res.json({status:'done'});
});
};
// api/posts/:postId/comments
exports.postComment = function(req,res){
var comment = new Comment({
content: req.body.content,
post: req.params.postId,
user: req.user._id
});
comment.save(function(err){
if(err){res.send(err);}
res.json({status:'done'});
});
};
Do I need to use a middleware? or do i need to do something in controller?
What you want is called "population" in Mongoose (see documentation), which basically works by storing references to other models using their ObjectId.
When you have a Post instance and a Comment instance, you can "connect" them like so:
var post = new Post(...);
var comment = new Comment(...);
// Add comment to the list of comments belonging to the post.
post.commentIds.push(comment); // I would rename this to `comments`
post.save(...);
// Reference the post in the comment.
comment.post = post;
comment.save(...);
Your controller would look something like this:
exports.postComment = function(req,res) {
// XXX: this all assumes that `postId` is a valid id.
var comment = new Comment({
content : req.body.content,
post : req.params.postId,
user : req.user._id
});
comment.save(function(err, comment) {
if (err) return res.send(err);
Post.findById(req.params.postId, function(err, post) {
if (err) return res.send(err);
post.commentIds.push(comment);
post.save(function(err) {
if (err) return res.send(err);
res.json({ status : 'done' });
});
});
});
};
I'm having a trouble to write a simple User and Tweet Schemas relation.
I created a middleware that will check a user token once he logged in. This is a token based authentication. The problem lies in the '/tweet' route section of how to save a tweet to a Logged in user.
schema.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
username: { type: String, required: true, index: { unique: true }},
password: { type: String, required: true, select: false },
level: String,
tweet: [{
type: Schema.Types.ObjectId, ref: 'Tweet'
}],
});
var TweetSchema = new Schema({
_creator: { type: Number, ref: 'User'},
title: String,
content: String
});
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('Tweet', TweetSchema);
api.js
let just assume that I have written '/login' route for log in a user and create a token above this middleware.
// The middleware for verifying user's token ( The user already login )
apiRouter.use(function(req, res, next) {
// do logging
console.log("Somebody just came to our app!");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
// check if token exist
if(token) {
jwt.verify(token, superSecret, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: 'Failed to authenticate user' });
} else {
// if everything is good save request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: 'No token provided' });
}
});
// another route for creating a tweet, in order to use this route, user's token must be verified.
apiRouter.route('/tweet')
.post(function(req, res) {
// The problem is I dont know how to get a user that just Login to create a tweet that will save in his user_tweet Schema.
var tweet = new Tweet({
_creator: ???,
title: req.body.title,
content: req.body.content
});
tweet.save(function(err) {
if(err) res.send(err);
res.json({ message: "New tweet has been added" });
});
});
Currently I'm having a trouble to create a tweet and saves it to a User that already log in and token has been verified. What should I write in '/tweet' route section to achieve my goal.
I'm trying to emulate a Twitter app for you guys information.
I don't know what you're using to generate your tokens, but the standard practice is to encode the user id as part of the token. So when you decode the token, like you've done here:
req.decoded = decoded;
req.decoded it should contain the user id, which you can then use to look up the user and check their permissions, or in your case, you'd use it to create your tweet object.
So something like this:
// create a json webtoken
var token = jwt.sign({
id = user._id,
name: user.name,
username: user.username
}, superSecret, {
expiresInMinute: 1440
});
Then do:
var tweet = new Tweet({
_creator: req.decoded.id,
title: req.body.title,
content: req.body.content
});