Mongoose Find() and then limit embedded documents - node.js

Every post has comments, and users can 'like' a comment. What Im trying to achieve is when a user is viewing a posts comments they will be able see the ones they've liked and not currently liked (allowing to like and unlike any comment)
Schema for Comments:
// ====================
// Comment Likes Schema
// ====================
var CommentLikes = new Schema({
userId:String,
commentId:String
})
// ==============
// Comment Schema
// ==============
var CommentSchema = new Schema({
postId:String,
authorId:String,
authorUsername:String,
authorComment:String,
likes:[CommentLikes],
created: {
type: Date,
default: Date.now
},
});
// Comment Model
var Comment = mongoose.model('Comment', CommentSchema, 'comment');
This seems like the best way to handle Comment Likes by using Embedded Documents.
Currently Im selecting all the post comments like so:
Comment.find({ postId:req.params.postId }).sort({ _id: -1 }).limit(20)
.exec(function (err, comments) {
if(err) throw err
// === Assuming Comment Likes Logic Here
// Return
return res.send({
comments:comments,
})
})
At the moment I'm returning all the likes back, irrespective of the current users Id. If the comment has 50k likes then this surely would be inefficient to send all the likes back, therefore I want to get all the comments but only the likes that have been created by the logged in user (using userId). What would be the most efficient way to do this? My first thought was to just run another find and get them that way? But surely can I not just run a find() on the parent then limit the Embedded Documents?

Related

I have two models in MongooseDB, how do I update the two at once using Node.js and React.js?

I have two models named user and post in my Mongoose database. I will like that when I update the user model, it reflects on the post model. For instance, I updated my username from the user model, I would like that update to take effect on the post model as well. I am using Node.js and React.
here is my code on Node.js for the user model
router.put("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){//we checked if the user id matched
if(req.body.password){
const salt = await bcrypt.genSalt(10);
req.body.password = await bcrypt.hash(req.body.password, salt);
}
try{
const updatedUser = await User.findByIdAndUpdate(req.params.id,{
$set: req.body,
}, {new: true});
//findbyidandupdate is an inbuilt method
res.status(200).json(updatedUser)
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
} else{
res.status(401).json("You can only update your account!")
}
});
I am assuming that you want to have user information attached with each post and when the user data is modified, the changes should reflect on subsequent requests for post data as well.
The functionality you are looking for is mongoose refs and populate.
These allow you to have reference to documents in a document.
In your case, we want a ref to a user document inside a post document.
So the schema for Post model could look like this:
const postSchema = new mongoose.schema({
...
user: { type: Schema.Types.ObjectId, ref: 'User' } // Here 'User' is the name of the UserModel
...
})
Now each post will contain a reference of a user.
The data that is being saved on each post doc as the user property is the _id of a user doc (not the complete document). So you can update the user docs without worrying about how it's going to affect the posts.
For retrieving the user information each time you retreive a post, you need to populate the post doc.
const post = await Post.
findOne(...).
populate('user').exec()
More information: https://mongoosejs.com/docs/populate.html

Populate in mongoose returns array of _ids

I'm creating simulation for goodreads by MERN stack
and when I'm using populate to retrieve books of specific user it returns empty array, I've done a lot of search but in vain
here's my model
const userSchema =new mongoose.Schema({
firstName:{
type:"string",required:true
},
books:[{
book:{type:mongoose.Schema.Types.ObjectId,ref:'Book'},rate:Number,shelve:''
}]});
And this is books model
const bookSchema =new mongoose.Schema({
title :{
type:"string",required:true
}});
And this is how I use populate
router.get("/one", (req, res, next) => {
User.find({firstName : "John"}).populate("books.book").exec(function (err, user) {
res.json(user)
});
})
and this is the resulted JSON
[{"_id":"5c70f299ef088c13a3ff3a2c","books":
[{"_id":"5c708c0077a0e703b15310b9"},{"_id":"5c708c0077a0e703b15310ba"},
{"_id":"5c708c0077a0e703b15310bb"},{"_id":"5c708c0077a0e703b15310bd"}]}]
I believe it's an issue with how your UserSchema is defined. My assumption is that including the rate and shelve in the definition of books is causing the problem.
Try removing those fields to start, and just populating books instead of books.book. If that works, then I would really reconsider putting those fields where you have them. In my own personal opinion, I think they seem better in the BookSchema since each book in the UserSchema has a rate and shelve anyways. Hope this helps!!

Looking for Best Approach For update one in Mongoose

I am still kind of new to Mongoose in general. I am building my blogging app which has backend based on Node and MongoDB, I am using Angular for frontend.
I am creating my Restful API which is supposed to allow user click on a post and update it. However, I don't know for sure whether I am doing it the right way here.
This is the schema for my post:
const mongoose = require('mongoose');
// Schema Is ONly bluePrint
var postSchema = mongoose.Schema({
title: {type: String, required: true },
content: {type: String, required: true},
}, {timestamps: true});
module.exports = mongoose.model("Post", postSchema);
In my angular service, I have this function to help me to send the http request to my backend server, the id for this function comes from backend mongoDB, title and content is from the form on the page
updatePost(id: string, title: string, content: string) {
console.log('start posts.service->updatePost()');
const post: Post = {
id: id,
title: title,
content: content
};
this._http.put(`http://localhost:3000/api/posts/${id}`, post)
.subscribe(res => console.log(res));
}
It appears to me that there are at least couple of ways of approaching this for creating my API
Method 1 ( works but highly doubt if this is good practice):
here I am passing the id retrieved from mongoDB back to server via my service.ts file to avoid the 'modifying immutable field _id' error
app.put("/api/posts/:id", (req,res)=>{
console.log('update api called:', req.params.id);
const post = new Post({
id: req.body.id,
title: req.body.title,
content: req.body.content
});
Post.updateOne({_id: req.params.id}, post).then( result=> {
console.log(result);
res.json({message:"Update successful!"});
});
});
Method 2 I consider this is more robust than method 1 but still I don't think its good practice:
app.put("/api/posts/:id", (req, res)=> {
Post.findOne(
{_id:req.params.id},(err,post)=>{
if(err){
console.log('Post Not found!');
res.json({message:"Error",error:err});
}else{
console.log('Found post:',post);
post.title=req.body.title;
post.content=req.body.content;
post.save((err,p)=>{
if(err){
console.log('Save from update failed!');
res.json({message:"Error",error:err});
}else{
res.json({message:"update success",data:p});
}
})
}
}
);
});
I am open to all opinions in the hope that I can learn something from guru of Mongoose and Restful : )
Justification to Choose findOneAndUpdate() in this scenario in simple words are as follow:
You can use findOneAndUpdate() as it updates document based on the
filter and sort criteria.
While working with mongoose mostly we prefer to use this function as compare to update() as it has a an option {new:
true} and with the help of that we can get updated data.
As your purpose here is to updating a single document so you can use findOneAndUpdate(). On the other hand update() should be
used in case of bulk modification.
As update() Always returns on of document modified it won't return updated documents and while working with such a scenario like
your we always returns updated document data in response so we
should use findOneAndUpdate() here

Mongo / Express Query Nested _id from query string

Using: node/express/mongodb/mongoose
With the setup listed above, I have created my schema and model and can query as needed. What I'm wondering how to do though is, pass the express request.query object to Model.find() in mongoose to match and query the _id of a nested document. In this instance, the query may look something like:
http://domain.com/api/object._id=57902aeec07ffa2290f179fe
Where object is a nested object that exists elsewhere in the database. I can easily query other fields. _id is the only one giving an issue. It returns an empty array of matches.
Can this be done?
This is an example and not the ACTUAL schema but this gets the point across..
let Category = mongoose.Schema({
name: String
})
let Product = mongoose.Schema({
name: String,
description:String,
category:Category
})
// sample category..
{
_id:ObjectId("1234567890"),
name: 'Sample Category'
}
// sample product
{
_id:ObjectId("0987654321"),
name:'Sample Product',
description:'Sample Product Description',
category: {
_id:ObjectId("1234567890"),
name: 'Sample Category'
}
}
So, what I'm looking for is... if I have the following in express..
app.get('/products',function(req,res,next){
let query = req.query
ProductModel.find(query).exec(function(err,docs){
res.json(docs)
})
})
This would allow me to specify anything I want in the query parameters as a query. So I could..
http://domain.com/api/products?name=String
http://domain.com/api/products?description=String
http://domain.com/api/products?category.name=String
I can query by category.name like this, but I can't do:
http://domain.com/api/products?category._id=1234567890
This returns an empty array
Change your query to http://domain.com/api/object/57902aeec07ffa2290f179fe and try
app.get('/api/object/:_id', function(req, res) {
// req._id is Mongo Document Id
// change MyModel to your model name
MyModel.findOne( {'_id' : req._id }, function(err, doc){
// do smth with this document
console.log(doc);
});
});
or try this one
http://domain.com/api/object?id=57902aeec07ffa2290f179fe
app.get('/api/object', function(req, res) {
var id = req.param('id');
MyModel.findOne( {'_id' : id }, function(err, doc){
console.log(doc);
});
})
First of all increase your skills in getting URL and POST Parameters by this article.
Read official Express 4.x API Documentation
Never mind I feel ridiculous. It works just as I posted above.. after I fixed an error in my schema.

Querying nested documents using Mongoose (MongoDB)

I am starting out with mongodb and having hard time trying to query nested documents. I have two schemas:
var LinkSchema = new mongoose.Schema({
url: String,
name: String
});
var UserSchema = new mongoose.Schema({
name: String,
links: [LinkSchema]
});
As you can see, I am just tying to build a simple bookmarking tool. Each user has a name and a collection of links. Each link has a name and a url.
Now, what I am trying to do is for example, see if a link already exists in someone's links array. I would like to be able to do something like this (Trying to get vlad's link collection and then see if the query link already belongs to the collection or not):
app.get("/:query", function(req, res){
User.findOne({"name":"vlad"}, function(err, user){
user.links.find({"url":req.params.query}, function(err, foundLinks){
if(foundLinks){
res.send("link already exists!");
} else {
res.send("link doesn't exist!");
}
});
});
});
Of course, this code doesn't work, because apparently I can't do a "user.links.find()". I guess I can just do a user.links.map to extract only urls and then run a membership query against it. But I think this would be far from the right solution. There's gotta be a way to do something like this natively using DB queries. Can someone help? Thank you!
You can query an embedded document in mongoose like this
User.find({'links.url':req.params.query}, function(err, foundUsers){
// ---
});
and to find the links that belong to the user "vlad", you can write
User.find({name:'vlad','links.url':req.params.query}, function(err, foundUsers){
// ---
});
This will do the trick.
To find a specific link that belongs to a specific user you can do this
User.find({name:'vlad','links.url':req.params.query}, { 'links.$': 1 }, function(err, foundUsers){
// ---
});

Resources