I have a problem with my mongoose schemas. I was able to populate one document from another, but I am unable to create a similar connection between other document.
I've been looking at this for a long time but I just don't see whats wrong. It seems to be setup correctly, but comments do not populate. I am using mongoose 5.4.5.
blogSchema
const mongoose = require('mongoose')
const blogSchema = mongoose.Schema({
title: String,
author: String,
url: String,
likes: Number,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
})
blogSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
const Blog = mongoose.model('Blog', blogSchema)
module.exports = Blog
commentSchema
const mongoose = require('mongoose')
const commentSchema = mongoose.Schema({
text: {
type: String,
minlength: 3,
present: true
},
blog: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Blog'
}
})
commentSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
const Comment = mongoose.model('Comment', commentSchema)
module.exports = Comment
userSchema
const mongoose = require('mongoose')
const uniqueValidator = require('mongoose-unique-validator')
const userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
minlength: 3,
present: true
},
name: String,
passwordHash: String,
blogs: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Blog'
}
],
})
userSchema.plugin(uniqueValidator)
userSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
delete returnedObject.passwordHash
}
})
const User = mongoose.model('User', userSchema)
module.exports = User
populate
router.get('/', async (request, response) => {
const blogs = await Blog.find({})
.populate('comment', { text: 1 })
.populate('user', { username: 1, name: 1 })
response.json(blogs.map(b => b.toJSON()))
})
I am able to populate user correctly to blogSchema, but populating Comment doesnt work. The order of the populate calls do not change the situation and if I call populate only for comment it doesn't work anyway.
I suppose that there is a problem with my schemas, but I just am unable to see it.
Well...In your blog it's called comments but you try to populate comment. I think that's the issue.
Related
I am still on this issue and I have implemented so far what I have seen online, still not working. I have two models named: PostSchema and UserSchema. PostSchema and Userschema both have a field called 'username'. So, I have created action that enables the user to update their profile. The challenge I have now is that the update made by the user which is stored in the UserSchema does not reflect on the PostSchema. For instance, I would like the username on the PostSchema to be updated to the current username on the UserSchema was the user updates or change their name from the UserSchema. I am trying to use the ref and populate features in mongoose. Probably, I am not doing it the right way.
With the current codes I have now, the posts are not fetching. If I remove the populate(' username', 'username').exec() line, it will fetch but the post.username will not change to user.username coming from the UserSchoma model.
Here are my codes:
I am still new and still learning, kindly help me pass this stage.
UserSchema model
const mongoose = require("mongoose"); //import mongoose
const UserSchema = new mongoose.Schema({
username:{ //I want the username here to update to post model too
type: String,
required: true,
unique: true
},
email:{
type: String,
required: true,
unique: true
},
password:{
type: String,
required: true
},
profilePicture:{
type: String,
default: "",
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("User", UserSchema);
PostSchema model
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:{ //this should reference user.username
type: Schema.Types.ObjectId, ref: 'User',
required: true,
},
categories:{
type: Array,
required: false
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema);
This is where I am getting the posts
//Get Post
router.get("/:id", async(req, res)=>{
try{
const post = await Post.findById(req.params.id);
populate(' username', 'username').exec()
res.status(200).json(post)
}catch(err){
res.status(500).json(err)
}
})
This is the client side code with React.js where I called the posts from the api
import { useLocation } from 'react-router';
export default function SinglePost() {
const location = useLocation()
const path = location.pathname.split("/")[2];
const [post, setPost] = useState({});
const [title, setTitle] = useState("")
const [description, setDescription] = useState("");
const [updateMode, setUpdateMode] = useState(false)
useEffect(() => {
const getPost = async () => {
try{
const response = await axios.get("/posts/"+path )
setPost(response.data);
setTitle(response.data.title);
setDescription(response.data.description);
setPostUser(response.data.username)
}catch(err){
}
};
return getPost()
}, [path]);
use $lookup and aggregation
aggregate object is like this
Post.aggregate([
{ $match: { _id: req.params.id } },
{
$lookup: {
from: "User",
localField: "username",
foreignField: "_id",
as: "username",
},
},
]).exec()
I have a Comment Model, User Model and Post model. In Post Model, there is a field called 'comment' and I referenced Comment model there. That way, every comment made on that post will be populated.
Now, if a user deletes any comment, that comments get deleted but the id referenced in the Post Model still remains. Though it is not active but it remains there. In a situation where you have many comments that get deleted, that Post collection with the Comment referenced field will look messy. Is there a way around this? I want once a comment is deleted, it should also delete anywhere it is referenced. Here are my codes:
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"
Codes that delete a comment
//comment delete
router.delete("/posts/:id/comment/:id", async (req, res) =>{
try{
const comment = await Comment.findById(req.params.id)
if(comment.author == req.body.author){
try{
await comment.delete()
res.status(200).json("Comment has been deleted")
}catch(err){
console.log(err)
}
}
else{
res.status(401).json("you can only delete your comment")
}
}catch(err){
console.log(err)
}
})
codes that populates comment in Post
//Get Post
router.get("/:id", async(req, res)=>{
try{
const post = await Post.findById(req.params.id).populate('username').populate({
path: "comments",
populate: {
path: "author",
}
})
See the attached image. You can see that comment field in Post collection is still with a comment ref that has been deleted. The comment is deleted from the Comment Collection. But I will also like to delete all places it is referenced.
my main language is not English, so I apologize for that
Some things need to be corrected
Post Model
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,
},
// Commented for testing
// 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);
Comment Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CommentSchema = new mongoose.Schema(
{
commentdescription: {
type: String,
required: true,
},
// Commented for testing
// author: {
// type: Schema.Types.ObjectId,
// ref: "User",
// },
postId: {
type: Schema.Types.ObjectId,
ref: "Post",
},
},
{ timestamps: true }
);
module.exports = mongoose.model("Comment", CommentSchema);
index.js
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json()); // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true }));
const port = 3000;
var mongoDB = "mongodb://127.0.0.1/my_database";
mongoose.connect(mongoDB);
const Post = require("./Model/Post");
const Comment = require("./Model/Comment");
// add post
app.post("/posts/add", async (req, res) => {
console.log(req.body);
const post = await Post.create(req.body);
res.status(200).json({
success: true,
data: post,
});
});
// add comment
app.post("/comment/add", async (req, res) => {
const comment = await Comment.create(req.body);
const post = await Post.findByIdAndUpdate(
{ _id: comment.postId },
{
$addToSet: { comments: comment._id },
}
);
res.status(200).json({
success: true,
data: comment,
});
});
// get all post
app.get("/posts", async (req, res) => {
const post = await Post.find({});
res.status(200).json({
post,
});
});
// delete comment
app.delete("/comment/:id", async (req, res) => {
const com = await Comment.findById(req.params.id);
console.log("postid", com.postId);
await Post.findByIdAndUpdate(
{ _id: com.postId },
{
$pull: { comments: com._id },
},
{ new: true }
);
await com.delete();
res.status(200).json({
success: true,
});
});
app.listen(port, () => {
console.log("server connect");
});
Models are not smart to understand your intention that because you
deleted the comment, they should be deleted from everywhere.
Computers are stupid, you have to explain to them face to face.
I am trying to use populate(), however it seems like it doesn't contain transactions in user.
Is there something wrong in my code?
The transaction Table contains userId. Therefore, I thought it would automatically contains array of transactions that matches with the userId.
User Table
Transaction Table
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
name:
{
type: String,
required: true
},
transactions: [
{
type: Schema.Types.ObjectId,
ref: 'Transaction'
}
],
},
{
timestamps: true
}
);
module.exports = mongoose.model('User', userSchema)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const transactionSchema = new Schema(
{
userId:
{
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
payer: String,
points:
{
type: Number,
reqruied: true
}
},
{
timestamps: true
}
)
module.exports = mongoose.model('Transaction', transactionSchema)
exports.getUsers = async (req, res, next) => {
User
.find()
//.findOne({ _id: "6009f3d8019a22479cb21a5d"})
.populate('Transaction')
.then(user => {
console.log(user)
})
}
In your User-Schema you've defined the transactions as transactions, so you need to populate it under this name:
User.find()
.populate('transactions')
.then(user => {
console.log(user)
})
It's possible that i'm just burned out, but I have the following models:
user
const mongoose = require('mongoose');
const validate = require('mongoose-validator');
const Post = require('./post');
let UserSchema = mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: {
type: String, required: true, lowercase: true, trim: true, unique: true, index: true,
validate: [validate({ validator: 'isEmail', message: 'Invalid Email!' })]
},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
})
module.exports = mongoose.model('User', UserSchema);
posts
const _ = require('lodash');
const mongoose = require('mongoose');
const User = require('./user');
let PostSchema = mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
title: { type: String, required: true },
body: { type: String, require: true }
})
PostSchema.post('save', async function (next) {
await User.update({ _id: this.user }, { $push: { posts: this._id } })
return next();
})
module.exports = mongoose.model('Post', PostSchema);
When trying to add a new post, the post save hook runs, but I get the error User.update is not a function (same goes for findOneAndUpdate, findOne, etc).
I can call user.update from the rest of the app without issues, so not sure whats happening here. Both models are in the same directory.
What you missed is that post middleware has the first argument as the "document" and not the next handler:
user.js
const { Schema } = mongoose = require('mongoose');
const userSchema = new Schema({
firstName: String,
lastName: String,
posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
post.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const postSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
title: String,
body: String
});
// note that first argument is the "document" as in "post" once it was created
postSchema.post('save', async function(doc, next) {
await User.update({ _id: doc.user._id },{ $push: { posts: doc._id } });
next();
});
index.js
const { Schema } = mongoose = require('mongoose');
const User = require('./user');
const Post = require('./post');
const uri = 'mongodb://localhost/posttest';
mongoose.set('debug', true);
mongoose.Promise = global.Promise;
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri);
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
let user = await User.create({ firstName: 'Ted', lastName: 'Logan' });
let post = new Post({ user: user._id, title: 'Hi', body: 'Whoa!' });
post = await post.save();
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Returns:
Mongoose: users.remove({}, {})
Mongoose: posts.remove({}, {})
Mongoose: users.insertOne({ posts: [], _id: ObjectId("5b0217001b5a55208150cc9b"), firstName: 'Ted', lastName: 'Logan', __v: 0 })
Mongoose: posts.insertOne({ _id: ObjectId("5b0217001b5a55208150cc9c"), user: ObjectId("5b0217001b5a55208150cc9b"), title: 'Hi', body: 'Whoa!', __v: 0 })
Mongoose: users.update({ _id: ObjectId("5b0217001b5a55208150cc9b") }, { '$push': { posts: ObjectId("5b0217001b5a55208150cc9c") } }, {})
Showing that the update fires with the correct detail.
In good design you really should avoid this and simply drop the posts array from the User model. You can always either use a virtual instead:
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'user'
})
Or just get the data via $lookup:
User.aggregate([
{ "$match": { "_id": userId } }
{ "$lookup": {
"from": Post.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "posts"
}}
])
Storing and maintaining arrays of related ObjectId values "on the parent" is kind of an "anti-pattern" and leads to unnecessary overhead such as writing in two places where you only need "one".
Also in general you should be opting for embedding "first", and only considering "referencing" if and when the usage pattern of the application actually demands it. Simply copying the same patterns of an RDBMS with a database engine that was not designed for that is not the best way to utilize it.
I am making a route in a Node server using Mongoose and Mongo which stores a comment in the comments array in blogPost (I will post model code). When I try to execute the query it gives me the following error:
Postman error
These are my models and the route:
blogPost model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const BlogPostSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
rating: Number,
title: String,
user: { type: Schema.Types.ObjectId, ref: 'user' },
board: {type: Schema.Types.ObjectId, ref: 'board'},
comments: [{
type: Schema.Types.ObjectId,
ref: 'comment'
}]
});
const BlogPost = mongoose.model('blogPost', BlogPostSchema);
module.exports = BlogPost;
comment model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
user: { type: Schema.Types.ObjectId, ref: 'user' },
rating: Number
// board: Board
});
// UserSchema.virtual('postCount').get(function(){
// return this.posts.length;
// });
const Comment = mongoose.model('comment', CommentSchema);
module.exports = Comment;
Route
routes.put('/blogPosts/:id/comment', function(req, res) {
const blogPostId = req.param('id');
const commentProps = req.body;
BlogPost.findById(req.params.id)
.then((blogPost) => {
blogPost.comments.push(commentProps);
blogPost.save();
})
.catch((error) => res.status(400).json(error))
});
Any help is greatly appreciated.
The problem is that you are pushing an entire comment object into an array that's only supposed to have objectids.
dnickless answer uses a solution with referencing, meaning you have a collection for blogposts and a collection for comments. The blogpost documents will refer to their comments with objectids.
You can also change the blogpost model to use embedding rather than referencing, meaning the comments will be a part of the blogpost documents as subdocuments. There are a couple of nice discussions regarding what's better here and here. The short answer is that it depends on the use case. You can choose what you want to use yourself.
Here's how embedding is done:
Blogpost model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const commentSchema = require('./comment.model');
const BlogPostSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
rating: Number,
title: String,
user: { type: Schema.Types.ObjectId, ref: 'user' },
board: {type: Schema.Types.ObjectId, ref: 'board'},
comments: [commentSchema]
});
const BlogPost = mongoose.model('blogPost', BlogPostSchema);
module.exports = BlogPost;
Notice how the comments array uses the comment schema rather than being an array of object ids. The comment model has to be changed slightly:
Comment model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
content: {
type: String,
validate: {
validator: (content) => content.length > 5,
message: 'Content must contain at least 6 characters.'
},
required: [true, 'Content must be filled in.']
},
user: { type: Schema.Types.ObjectId, ref: 'user' },
rating: Number
// board: Board
});
// UserSchema.virtual('postCount').get(function(){
// return this.posts.length;
// });
module.exports = CommentSchema;
const Comment = mongoose.model('comment', CommentSchema); was removed. The schema should no longer be registered and comments will not have their own collection. The schema is now being exported rather than the registered model.
The original code for adding comments should work after that. Comments will be a part of the blogpost document, not be in their own collection.
You don't want to push the entire comment into the comments array but just its _id. So something like this (untested):
// first save the comment
Comment.create(commentProps, function(err, comment) {
if(err)
{
// save the world
}
else
{
BlogPost.update({ _id: req.param('id') }, { $push: { comments: comment._id } }, function(err, numberAffected, raw) { /*...*/ });
});