Querying nested documents using Mongoose (MongoDB) - node.js

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){
// ---
});

Related

deep find() query in Mongoose

so my schema looks like something like this
var PagesSchema = new mongoose.Schema({
citiesList:{
country:String,
city:String
}
});
i want to access citiesList in my route so i tried
app.get("/", function(req,res){
Pages.find({"citiesList"}, function(err,citiesList){
if(err){
console.log(err);
}else{
res.render('landing',{citiesList:citiesList});
}
});
});
but it's not working any advice please ?
That query is searching for any document that look like {citiesList: "citiesList"}. If you want all "citiesList", you can instead use {} to find all documents, and a projection to limit it to the citiesList field: Pages.find({}, "citiesList", cb)
https://mongoosejs.com/docs/api.html#model_Model.find
If instead you want only those subdocuments in a formatted list, you can use an aggregation to process them

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.

Mongoose Find() and then limit embedded documents

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?

Mongoose nested reference population

I'm having trouble understanding some of the concepts behind mongoose's populate methods. I had an embedded approach working first, although, as I feared a big overhead of data and out-of-sync documents going around I tried changing the paradigm to ref other documents.
My Schema is similar to the following (removed irrelevant properties):
var userSchema = mongoose.Schema({
name: {type: String, default:''},
favorites:{
users:[{type: Schema.Types.ObjectId, ref: this}],
places:[{type: Schema.Types.ObjectId, ref: PlaceSchema}]
}
});
module.exports = mongoose.model('User', userSchema);
Now I'm trying to get a User's favorites like this:
User.findOne({_id: currentUser}).exec(function(err, user){
console.log(user);
if (err)
throw err;
if(!user){
console.log("can't find user");
}else{ // user found
user.populate('favorites.users');
user.populate('favorites.places');
// do something to the 'user.favorites' object
}
});
Although this doesn't work as intended, as both user.favorites.users and user.favorites.places come up undefined.
I thought that I could populate as above, but apparently, that's not the case. From what I read, I must be missing something indicating (maybe) the model of the ref'ed document? This flow is very new to me and I'm kinda lost.
Is there anyway I can get an array of users and places by populating my query result as above? I tried populate chained with exec and it doesn't work either. Is there a better way to achieve this result?
EDIT: In case it's needed, in the DB, a User document shows up as:
{
"_id": "56c36b330fbc51ba19cc83ff",
"name": "John Doe",
"favorites": {
"places": [],
"users": [
"56b9d9a45f1ada8e0c0dee27"
]
}
}
EDIT: A couple more details... I'm currently storing/removing the reference ObjectIds like this (note targetID is a String):
user.favorites.users.push({ _id: mongoose.Types.ObjectId(targetID)});
user.favorites.users.pull({ _id: mongoose.Types.ObjectId(targetID)});
Also, I need to populate the users and places with their respective documents aswell, I think that might not be clear in my original question.
Well, I figured out what I needed by paying proper attention to the docs (and also with #DJeanCar 's (+1'd) help/pointers).
By Mongoose's docs regarding Populating across multiple levels, I've reached this solution:
User.findOne({_id: currentUser})
.populate({path:"favorites.users", populate: "name"})
.populate({path:"favorites.places", populate: "name"})
.exec(function(err, user){
if(err)
throw err;
if(!user){
console.log("couldnt find source user");
}else{
// here, user.favorites is populated and I can do what I need with the data
}
});
Also, from what I could tell, you can also pass select: "field field field" in populate()'s options, should you need to filter the document fields you require after population.
Hope this helps someone with similar issues!
Try:
User
.findOne({_id: currentUser})
.populate('favorites.users')
.populate('favorites.places')
.exec( function (err, user) {
// user.favorites.users
// user.favorites.places
});

Updating a model instance

I want to understand what the correct way to update a model instance is, using mongoose, ie:
Having:
User = {
username: {
type: String,
indexed: true
},
email: {
type: String,
indexed: true
},
name: String,
.........
};
I'm sending the whole form through ajax to a controller.
So far, i know of two options:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
User.findOne({_id: id}, function(err, user){
user.name = req.body.name;
....
....
user.save();
});
});
or:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
delete req.body._id
User.update({_id: id}, req.body, function(err){
.....
};
});
Both ways have disadvantages:
In the first approach i have to map all properties one by one;
In the second approach i have to delete all properties that can't be changed;
there is a third possible approach that would make me send from client-side, only the changed properties, but i think that would be a big hassle to.
Is there a good, standardized way that i'm not seeing, to do this?
Thanks
This is the approach I typically use, it involves a small npm package called mongoose-mass-assignement:
https://github.com/bhelx/mongoose-mass-assignment
The docs are pretty self explanatory, easy to use. It basically does the delete for you. So you add protected:true to your model, and then it deletes those properties for you. I like this approach because it still allows me to use the req.body as is. You can use this for updates and inserts as well.
A variant of the first approach is to use underscore's extend method to apply all properties of the body to your model instance:
app.put('/users/', function(req, res){
var id = ObjectId(req.body._id);
User.findOne({_id: id}, function(err, user){
_.extend(user, req.body);
user.save();
});
});
However, be sure to delete any properties you don't want the user to be able to set from req.body first.

Resources