MongoDB and Express - dynamic queries for routes with several id's - node.js

App is using Node and Express, and I have a MongoDB document with this structure, from db.users.find().pretty():
{
"_id" : ObjectId("hash1"),
"username" : "user1",
"notes" : [
{
"_id" : ObjectId("hash2"),
"data" : "This is note 1"
}
],
}
{
"_id" : ObjectId("hash3"),
"username" : "user2",
"notes" : [
{
"_id" : ObjectId("hash4"),
"data" : "This is note 2"
}
],
}
I need to remove a notes subdocument.
Using findByIdAndUpdate and pull, the route is:
app.delete('/users/:id/notes/:id', callback );
and the query:
var fieldsToSet = {
$pull: {notes: {_id: req.params.id}}
};
var options = { new: true };
req.app.db.models.User
.findByIdAndUpdate(req.user._id, fieldsToSet, options, callback)
I have no problem if I logged in as user1 and want to delete a note with hash2 ObjectId (see this as as a real hash generated by MongoDB), but if I still logged in as user1 and I want to delete a note with hash4 there is a problem, because I will always get a user._id with hash1. And i cant use req.params.id twice, it will always return me the last :id .
How can I get both id's dynamically?
Thanks

You need to have two different params for this, which makes sense since they're two things:
app.delete('/users/:userId/notes/:noteId', callback );
Then you can access the one you want with req.params.nodeId or req.params.userId

Related

findOneAndUpdate. Select array of messages, select message object and update likes array inside it

I use mongoose findOneAndUpdate method, but could't achieve the result. I always get the same object back. No errors. Here's my code.
User object:
{
"_id" : ObjectId("6273b64b607b1d228795f067"),
"username" : "User1",
"mail" : "user#inbox.com",
"password" : "$2b$12$NrJ9E8xSz1iCfmIPY0snC.e6x4B/ymqtH.at9uUWaCXTQUwJi1eem",
"messages" : [
{
"message" : "Brand new message",
"date" : 1652508162106.0,
"author" : "User1",
"likes" : []
}
],
"__v" : 16
}
Here's my backend code:
const {author} = req.params;
const {mssg, whoLiked} = req.body;
User.findOneAndUpdate({username: author, messages: mssg}, {$push: {likes: whoLiked}}, {new: true}, (err, user)=>{
err && console.log(err);
res.send(user)
})
})
Nothing udpates. Maybe I try to pick the message the wrong way... I know, it's possible to do this in one action. But I can't figure it out. What'd you say guys?
Ok, I finaly figured it out. Here's the code to push new person whoLiked into the likes array.
router.post("/:author/likeThePost", (req,res)=>{
const {author} = req.params;
const {mssg, whoLiked} = req.body;
console.log(mssg, whoLiked);
User.findOneAndUpdate({username: author, "messages.date": mssg.date}, {$push: {"messages.$.likes": whoLiked}}, {new: true}, (err, user)=>{
err && console.log(err);
res.send(user.messages)
})
})
Some comments:
I use mongoose findOneAndUpdate method. As far as I know there's the same node.js method, but the code might be different.
Let's break down the code a bit:
first you find the user who wrote the original message by username: "Some user name"
then you pick the message you need by some unique value. In my case this is just the timestamp: "messages.date": mssg.date . Same user just can't post two messages with the same Date.now() timestamp.
then you do what you need with the message've been picked:{$push: {"messages.$.likes": whoLiked}} Second dollar sign stands for picking right message. Looks like it's the index of it.

findByIdAndDelete doesn't actually delete a document, just nulls the value for each key

Newbie here using mongoose in node.js/express with MongoDB to try to build my own wedding website. I have in my app.js:
var Guest = require("./models/guest");
...
app.delete("/guests/:id", (req, res)=>{
Guest.findByIdAndDelete(req.params.id, (err, deletedGuest)=>{
if(err){
console.log(err);
res.redirect("/guests");
} else {
console.log("deleted: " + deletedGuest);
res.redirect("/guests");
}
})
})
with the model
var mongoose = require("mongoose");
var guestSchema = new mongoose.Schema({
name: String,
email: String,
isComing: Boolean,
})
module.exports = mongoose.model("Guest", guestSchema);
And there's an html table of guests with a delete button next to each one. So what happens is if I try to delete a guest by clicking the button next to their name, it sets that guest's name, email, and isComing to null, but doesn't delete it. In other words, for the guest:
{ "_id" : ObjectId("5ef811d5ea932d06d38537f5"), "name" : "Steve Stevenson", "email" : "steve#steve.com", "isComing" : true, "__v" : 0 }
If I press "delete," I get:
{ "_id" : ObjectId("5ef811d5ea932d06d38537f5"), "name" : null, "email" : null, "isComing" : null, "__v" : 0 }
The ID stays the same, it just removes all the values from the other keys, except the "__v" field. (Incidentally, I also have no idea what that is). Nothing gets logged in the console- no error or deleted guest. Any idea what I'm doing wrong? Other general advice is also welcome-- I'm trying to learn here.
It turns out the problem was I forgot to set method-override. So it was just sending a blank post request with that ID— hence the null values for each key. Whoops.

MongoDb Node.js Driver - findOneAndUpdate() behavior without a field given

I noticed a strange behavior with the mongodb node.js driver findOneAndUpate()...
I mistakenly gave it just a objectId string....thinking it would default to searching by _id field of a document.... so, when I used
User.prototype.updatePetArray = function(user, petElement) {
return this.collection.findOneAndUpdate(user,
{ $push: { pets: petElement } },
{ returnOriginal: false,
maxTimeMS: QUERY_TIME});
}
it pulled up and modified this document, which does not have this number at all:
{ "_id" : ObjectId("56d4e2a381c9c28b3056f792"), "username" : "bob123", "location" : "AT", ...}
Why did it modify this document when 56d4d35881c9c28b3056f78a is not in the document?
After I test it following your code with one non-exist ObjectID,
var col = db.collection('users');
col.findOneAndUpdate('56cd129222222', {fname: 'Daved'}, function(err, r) {
if (err)
console.log(err);
else
console.log(r);
db.close();
});
As a result the first document in the collection was changed .
Per this doc
findOneAndUpdate(filter, update, options, callback)
filter: Specify an empty document { } to update the first document returned in the collection.
So I think this non-exist ObjectId is consider to the same behavior with {}
After reviewing the source code of findAndModify, eventually, the
// Execute the command
self.s.db.command(queryObject
is invoked, and the queryObject is
var queryObject = {
'findandmodify': self.s.name
, 'query': query
};
So I test runCommand in mongo shell with non-exist ObjectId as below, as result, the first document is returned.
> db.users.runCommand({findandmodify: 'users', query: '123ddae344', update: true
})
{
"lastErrorObject" : {
"updatedExisting" : true,
"n" : 1
},
"value" : {
"_id" : ObjectId("56c687275d81735019263d1f"),
"fname" : "Daved"
},
"ok" : 1
}
The docs keep saying the filter parameter should be an object.
The wrong behavior is likely to some side effect of mis-interpreting the value, being a string not an object (and maybe a truthy value, non-empty string).

Data model reference using ObjectID in Mongoose

I have two mongoose models users and requests. One user can have multiple requests. To link these two data models, I am referencing the objectID of the user in the Request model. However when I am trying to fetch all the requests for a particular user ID, I don't get any result.
Users
var UserSchema = new Schema({
name: String,
username: String
});
module.exports = mongoose.model('User', UserSchema);
Requests
var RequestSchema = new Schema({
type: String,
user_id: { type : mongoose.Schema.Types.ObjectId, ref: 'User'}
});
module.exports = mongoose.model('Request', RequestSchema);
Below is the sample code which fetches the requests based on the User ID passed in the URL
exports.getRequests = function (req, res, next) {
var userId = req.params.id;
console.log('USER ID'+userId);
Request.findOne({'user_id': 'kenan'}, function (err, request) {
if (err) return next(err);
if (!requests){
console.log('NO REQUEST FOUND');
return res.send(401);
}
res.json(request);
});
};
I know I am using findOne which will return me a single resource. However this is failing and I am not getting any result. I have in my DB the data populated. Below is the URL for fetching the requests.
http://localhost:9000/api/V1/users/547ee1e82e47bb982e36e433/requests
Please help suggest as to what I am doing wrong over here.
Thanks
You need to populate the references to the users once you do a find().
Once you find all the requests, you need to resolve the references to
the users. Use populate to resolve the references.
After resolving, filter the requests based on the name that you
are searching for.
Updated Code:
Request
.find().populate({'path':'user_id',match:{'name':'kenan'}})
.exec(function (err, resp) {
var result = resp.filter(function(i){
return i.user_id != null;
});
console.log(result);
})
OK, the above answer is correct and thanks for your response. I incorrectly populated my Requests collection. This is just for every one's else knowledge. When you have a Object ID reference as one of the fields in the collection then make sure that you are inserting the ObjectID object instead of just the numerical value.
Wrong
{ "_id" : ObjectId("54803ae43b73bd8428269da3"), "user_id" : "547ee1e82e
47bb982e36e433", "name" : "Test", "service_type" : "test", "version" : "10"
, "environment" : "test", "status" : "OK", "__v" : 0 }
Correct
{ "_id" : ObjectId("54803ae43b73bd8428269da3"), "user_id" : ObjectId("547ee1e82e
47bb982e36e433"), "name" : "test", "service_type" : "test", "version" : "10"
, "environment" : "test", "status" : "OK", "__v" : 0 }

MongooseJS - How to find the element with the maximum value?

I am using MongoDB , MongooseJS and Nodejs.
I have a Collection ( called Member ) with the following Fields -
Country_id , Member_id , Name, Score
I want to write a query which returns the Member with the max Score where Country id = 10
I couldnt find suitable documentation for this in MongooseJS.
I found this at StackOVerflow ( this is MongoDB code )
Model.findOne({ field1 : 1 }).sort(last_mod, 1).run( function(err, doc) {
var max = doc.last_mod;
});
But how do I translate the same to MongooseJS ?
Member
.findOne({ country_id: 10 })
.sort('-score') // give me the max
.exec(function (err, member) {
// your callback code
});
Check the mongoose docs for querying, they are pretty good.
If you dont't want to write the same code again you could also add a static method to your Member model like this:
memberSchema.statics.findMax = function (callback) {
this.findOne({ country_id: 10 }) // 'this' now refers to the Member class
.sort('-score')
.exec(callback);
}
And call it later via Member.findMax(callback)
You do not need Mongoose documentation to do this.
Plain MongoDb will do the job.
Assume you have your Member collection:
{ "_id" : ObjectId("527619d6e964aa5d2bdca6e2"), "country_id" : 10, "name" : "tes2t", "score" : 15 }
{ "_id" : ObjectId("527619cfe964aa5d2bdca6e1"), "country_id" : 10, "name" : "test", "score" : 5 }
{ "_id" : ObjectId("527619e1e964aa5d2bdca6e3"), "country_id" : 10, "name" : "tes5t", "score" : -6 }
{ "_id" : ObjectId("527619e1e964aa5d2bdcd6f3"), "country_id" : 8, "name" : "tes5t", "score" : 24 }
The following query will return you a cursor to the document, you are looking for:
db.Member.find({country_id : 10}).sort({score : -1}).limit(1)
It might be faster to use find() than findOne().
With find().limit(1) an array of the one document is returned. To get the document object, you have to do get the first array element, maxResult[0].
Making Salvador's answer more complete ...
var findQuery = db.Member.find({country_id : 10}).sort({score : -1}).limit(1);
findQuery.exec(function(err, maxResult){
if (err) {return err;}
// do stuff with maxResult[0]
});
This is quick and easy using the Mongoose Query Helpers.
The general form for this could be:
<Your_Model>.find()
.sort("-field_to_sort_by")
.limit(1)
.exec( (error,data) => someFunc(error,data) {...} );
tldr:
This will give you an array of a single item with the highest value in 'field_to_sort_by'. Don't forget to access it as data[0], like I did for an hour.
Long-winded: Step-by-step on what that string of functions is doing...
Your_Model.find() starts the query, no args needed.
.sort("-field_to_sort_by") sorts the everything in descending order. That minus-sign in front of the field name specifies to sort in descending order, you can discard it to sort in ascending order and thus get the document with the minimum value.
.limit(1) tells the database to only return the first document, because we only want the top-ranked document.
.exec( (error,data) => someFunc(error,data) {...} ) finally passes any error and an array containing your document to into your function. You'll find your document in data[0]
You can also use the $max operator:
// find the max age of all users
Users.aggregate(
{ $group: { _id: null, maxAge: { $max: '$age' }}}
, { $project: { _id: 0, maxAge: 1 }}
, function (err, res) {
if (err) return handleError(err);
console.log(res); // [ { maxAge: 98 } ]
});

Resources