Node JS mongoose create a blog post commenting system - node.js

I am trying to build a simple blog with a commenting system with mongoose and express. There is no issse of creating and posting blogs here and each post can be displayed correctly. However, there are some issues associated with comments and each blog. The relationship between comments and blog post was established by applying mongoose.Schema.Types.ObjectId in the post Schema and the comments has been created to store array of comments ids. I think the schema structures are correct and there might be some problems in my code for routing and please help, thanks.
// Post Schema
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
trim: true,
required: true
},
text: {
type: String,
trim: true,
required: true
},
date: {
type: Date,
default: Date.now
},
comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}]
})
postSchema.virtual('url').get(function(){
return '/post/' + this._id
})
module.exports = mongoose.model('Post', postSchema);
// Comment Schema
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
text: {
type: String,
trim: true,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Comment', commentSchema);
// Router
const express = require('express');
const Post = require('../models/post');
const Comment = require('../models/comment');
const router = new express.Router();
// Get comments
router.get('/post/:id/comment', (req, res) => {
res.render('post-comment', {title: 'Post a comment'})
})
// Create and Post comments, this is where I think I made mistakes
router.post('/post/:id/comment', async (req, res) => {
const comment = new Comment({text: req.body.text});
const post = await Post.findById(req.params.id);
const savedPost = post.comments.push(comment);
savedPost.save(function(err, results){
if(err) {console.log(err)}
res.render('post_details', {title: 'Post details', comments:
results.comments})
} )
})
// Get each post details.
// Trying to display comments, but it is all empty and I realized
// the comments array is empty, I can see the comments create in
// mongodb database why is that?????
router.get('/post/:id', (req, res) => {
Post.findById(req.params.id)
.populate('comments')
.exec(function(err, results) {
if(err) {console.log(err)}
res.render('post_details', {title: 'Post details', post:
results, comments: results.comments})
})
})
router.get('/new', (req, res) => {
res.render('create-post', {title: 'Create a post'})
})
router.post('/new', (req, res) => {
const post = new Post({
title: req.body.title,
text: req.body.text
});
post.save(function(err) {
if(err) {console.log(err)}
res.redirect('/')
})
})
router.get('/', (req, res) => {
Post.find()
.exec(function(err, results) {
if(err) {console.log(err)}
res.render('posts', {title: 'All Posts', posts: results})
})
});
module.exports = router;

It has been a few days after I post this question and I have received no answer so far. But I have figure out why my code is not working and this post will try to answer this question I asked a few days ago and hope it can help some who is struggling the same issue.
My problems here are each comment created can not been pushed into the array of comments in Post, so the comments can not be displayed as the array is empty.
If you look at my code for Schema, you might realised that I made mistake for comment schema as I did not define a post key value pair, so the correct comment and post schema should be as below. The logic here is that each blog post can has multiple comments below, so the comments in post Scheme should be created as a array, but each comment can only below to one of the post, therefore the post key value pair in comment schema should be only in a object
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
text: {
type: String,
trim: true,
required: true
},
date: {
type: Date,
default: Date.now
},
// each comment can only relates to one blog, so it's not in array
post: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
})
module.exports = mongoose.model('Comment', commentSchema);
and the post schema should be as below
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
trim: true,
required: true
},
text: {
type: String,
trim: true,
required: true
},
date: {
type: Date,
default: Date.now
},
// a blog post can have multiple comments, so it should be in a array.
// all comments info should be kept in this array of this blog post.
comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}]
})
postSchema.virtual('url').get(function(){
return '/post/' + this._id
})
module.exports = mongoose.model('Post', postSchema);
Another important thing I did not do was, every time a comment was posted, this comment should be pushed into the post.comments array. I did not do this step, that's why my array was always empty and there is no comment to display. the code to correct this is go to file commentRouter.js (I have created both of postRouter.js and commentRouter.js), add code below
router.post('/post/:id/comment', async (req, res) => {
// find out which post you are commenting
const id = req.params.id;
// get the comment text and record post id
const comment = new Comment({
text: req.body.comment,
post: id
})
// save comment
await comment.save();
// get this particular post
const postRelated = await Post.findById(id);
// push the comment into the post.comments array
postRelated.comments.push(comment);
// save and redirect...
await postRelated.save(function(err) {
if(err) {console.log(err)}
res.redirect('/')
})
})
The above is how I fixed my error, hope it can help someone.
Thank you for reading

Related

What is wrong with my React.js code for blog comment feature

I created a comment feature in my node.js blog application. I am using Mongodb data base. Everything works fine in postman. I can create new comments. However, I am trying to replicate the same in my client side via React.js. I think I have exhausted all my ideas. Here is the error message I keep getting each time I try to create a new comment from client side: This error is console.log from the catch(err) in backend.
undefined
CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Post"
Post Model
//creating the user models for the database
const mongoose = require("mongoose"); //import mongoose
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema(
{
title:{
type: String,
required: true,
unique: true,
},
description:{
type: String,
required: true,
},
postPhoto:{
type: String,
required:false,
},
username:{
type: Schema.Types.ObjectId,
ref: 'User'
},
categories:{
type: Array,
},
comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
unique: true,
}]
}, {timestamps: true},
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema); //the module name is "Post"
Comment Model
const mongoose = require("mongoose"); //import mongoose to be used
const Schema = mongoose.Schema;
const CommentSchema = new mongoose.Schema(
{
commentdescription:{
type: String,
required: true,
},
author:{
type: Schema.Types.ObjectId,
ref: 'User',
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("Comment", CommentSchema); //the module name is "Post"
create new comment and push to post codes
//creating catergory logic
router.post("/posts/:id/comment", async (req, res) =>{
const newComment = new Comment(req.body);//we create a new comment for the database
try{
//we need to try and catch the new comment and save it
const currentPost = await Post.findByIdAndUpdate(req.params.id)//we need to find the post that has the comment via the id
currentPost.comments.push(newComment)//we need to push the comment into the post
await newComment.save();
await currentPost.save()//we saved the new post with the comment
res.status(200).json(currentPost)
}catch(err){
console.log(err)
res.status(500).json(err)
}
})
What I have written in React
const [commentdescription, setCommentDescription] = useState('')
const [postId, setPostId] = useState()
//function to create comment
const handleComment = async ()=>{
const newComment = {
_id: postId,
author: user._id,
commentdescription,
};
try{
await axios.post("/posts/:id/comment", newComment);
}catch(err){
console.log(err)
}
}
Everything works fine in postman. I know I am missing something in react.
You need to pass actual ID to that POST request url.
await axios.post(`/posts/${postId}/comment`, newComment);
When POSTing to /posts/:id/comment, your parsed value (req.params.id) will literally be :id, which cannot be casted as ObjectId.

Nodejs TypeError: Cannot read property 'push' of undefined

hey i am new here in nodejs and mongodb, i tyied to push comments on post in my social media project..
Here is my controller ,it shows error while pushing comments in mongodb
TypeError: Cannot read property 'push' of undefined
const Comment = require('../models/comment')
const Post = require('../models/post')
module.exports.create = function(req,res){
Post.findById(req.body.post, function(err ,post){
if(post){
Comment.create({
content: req.body.content,
post: req.body.post,
user: req.body._id
},function(err, comment){
if(err){console.log("error in pushing comment")}
post.comments.push(comment),
post.save()
res.redirect('/')
})
}
})
}
this is my comments schema
const mongoose = require('mongoose')
const commentSchema = new mongoose.Schema({
content: {
type: String,
required: true
},
//comments belongs to user
user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
post : {
type: mongoose.Schema.Types.ObjectId,
ref : 'Post'
}
},{
timestamps: true
})
const Comment = mongoose.model('Comment' , commentSchema)
module.exports = Comment
I recommend that you use Promises instead of callback functions. It will make your code way more readable. Monogoose findOneAndUpdate could be handy here.
As for the error, you should make a console.log(post.comments) to see the value for yourself.
We should see the model of Post. It should contain an array of Comments
const mongoose = require('mongoose')
const Comment = require('./comment.model.js') // Change the path
const postSchema = new mongoose.Schema({
// comments belongs to post
comments : {
type: [Comment]
},
// Other attributes here
},{
timestamps: true
})
const Post = mongoose.model('Post' , postSchema)
module.exports = Post
You'll end up with something like this:
const Comment = require('../models/comment')
const Post = require('../models/post')
module.exports.create = function (req, res) {
Post.findById(req.body.post).then((post) => {
if (post) {
Comment.create({
content: req.body.content,
post: req.body.post,
user: req.body._id
}).then((comment) => {
Post.findOneAndUpdate(
{ _id: req.body.post },
{ $push: { comments: comment } }
).then(() => {
res.redirect('/')
}).catch((error) => console.log(error))
})
}
}).catch((error) => console.log(error))
}

how to reference a model in another model using mongoose?

I'm pretty new to node and mongoose, still learning a lot. Basically I am trying to create a forum page. I have a forumpost schema and I have recently added in a new field that I would like to show which user posted it. I have read other questions on this online and I was able to follow the code on there however mine is still not working. When i check my data in atlas it is still missing the new 'submitted by' field that I added. I have already deleted the 'collection' and have started over but it is still missing. Any help would be appreciated. Heres my models below as well as a screencap of how the data is being posted to the db.
**Post Form Schema**
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
required: true,
},
submittedBy: { *(this is where I would like to get the user who submitted the form)*
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
extraInfo: {
type: String,
default: 'Other info goes here',
}
})
const Post = mongoose.model('Post', PostSchema);
module.exports = Post;
**Users Form Schema**
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
EDIT: heres my newpost route
const express = require('express');
const Post = require('../models/post');
const router = express.Router();
const {ensureAuthenticated} = require("../config/auth.js");
router.get('/', ensureAuthenticated, (req, res) => {
res.render('newPost')
})
router.post('/', ensureAuthenticated, (req, res) => {
const post = new Post(req.body);
console.log(req.body)
post.save()
.then((result) => {
res.redirect('/dashboard')
})
.catch((err) => {
console.log(err)
})
})
module.exports = router;
If I'm not mistaken, you validate if is authenticated with the "ensureAuthenticated" middleware (the user ID should be there) but when creating the "Post" you only do it with the body data.
It is something like this ( you should replace "userId" with your property name):
const post = new Post({ ...req.body, submittedBy: userId })

Filter collection on associated field ObjectId

SEE EDIT AT BOTTOM OF QUESTION.
I have a Node.js Express web application using MongoDB and Mongoose with collections for articles and comments. They have a one-to-many association where one article can have many comments.
The mongoose model schema is as follows:
// models/article
const mongoose = require('mongoose');
const articleSchema = new mongoose.Schema({
title: { type: String },
content: { type: String },
}, {timestamps: true});
module.exports = mongoose.model('Article', articleSchema);
and
// models/comment.js
const mongoose = require('mongoose');
const commentSchema = new mongoose.Schema({
content: { type: String },
article: { type: mongoose.Schema.Types.ObjectId, ref: 'Article' },
}, {timestamps: true});
module.exports = mongoose.model('Comment', commentSchema);
I have a route with a parameter for the article id
// routes.js
router.get('/articles/:articleId/comments', commentsController.list);
And a controller with a callback function to query the database and return the comments with the given article id. It uses the mongoose find() method filtering on the article id taken from the route parameter.
// controllers/commentsController.js
exports.list = (req, res, next) => {
Comment.find({ article: req.params.articleId })
.exec((err, comments) => {
res.render('comments/list', { title: 'Comments', comments: comments });
});
};
But this turns up no results. Just experimenting I can see that the req.params.articleId is a string and any comment.article is an object so they match with a loose comparison == but not a strict comparison === unless I convert comment.article.toString(). Anyway, what is the proper way to do such a query. All my attempts have failed.
EDIT: I found the problem. The code above is as it should be. The issue must be related to how I seeded the DB which I did directly in MongoDB. I deleted all those records and just added them from the application and it works with the code above.
One way to approach this is to add the comments to your article model.
const mongoose = require('mongoose');
const articleSchema = new mongoose.Schema({
title: { type: String },
content: { type: String },
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
}, {timestamps: true});
articleSchema.set('toJSON', {
transform: (document, returnedObject) => {
const article = returnedObject
article.id = article._id.toString()
delete article._id
}
})
module.exports = mongoose.model('Article', articleSchema);
Then get the comments in one of these ways:
const router = require('express').Router()
const Article = require('../models/article')
const Comment = require('../models/comment')
// article with comments
router.get('/:id', async (request, response, next) => {
try {
const article = await Article.findById(request.params.id)
.populate(
'comments', {
content: 1
}
)
response.json(article.toJSON())
} catch (err) {
console.log(err)
}
})
// list of comments belonging to an article
router.get('/:id/comments', async (request, response, next) => {
try {
const article = await Article.findById(request.params.id)
if (!article) {
response.status(404).json({ error: 'invalid request' })
}
const comments = await Comment.find({ article: request.params.id })
.populate(
'article', {
title: 1
}
)
response.json(comments.map(comment => comment.toJSON()))
} catch (err) {
console.log(err)
}
})
module.exports = router

Building a simple news feed in node + Mongodb + Redis

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#

Resources