Building a simple news feed in node + Mongodb + Redis
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#