Mongo / Express Query Nested _id from query string - node.js

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.

Related

Cast error while executing a mongoose query

I am executing a query in mongoose where I need to find all the users in my database and sort them and limit the results to 10.
My query route is:(the route is "/user/top")
router.get('/top', middleware.ensureAuthenticated, function (req, res) {
User.find({}).sort({bestScore: 1}).limit(10).exec(function (err, result) {
if(err){
res.json({
status:"error",
data:err
});
}
else {
res.json({
status:"ok",
data: result
})
}
})
});
My User model:
var UserSchema = new mongoose.Schema({
image: String,
displayName: String,
bestScore:Number,
});
The error while i call the url from postman
EDIT 1:
Output of mongodb query:
You can see that my _id is of type ObjectId .
Your data probably contains a document which looks like this:
{
_id: "top",
...
}
Since Mongoose expects that value to be an ObjectId by default you get this error. What you can do is map the _id field explicitly as a string:
var UserSchema = new mongoose.Schema({
_id: String,
image: String,
displayName: String,
bestScore:Number,
});
The issue you had is that your search route is going through findbyId. if you truly wanna search/find all users without using an Id, you need to make sure in your controller, your search comes before your getbyId. same also in your route. I had the same error here and I was able to solve it from my route by putting my search function before getbyId.
Also, when you are using postman, kindly enter the search parameter as seen in my own case study below;
take note the path and my parameter key and value:

Cannot read from database using mongoose

This is my GET request
var Catalog = mongoose.model('Catalog');
router.get('/catalog', function(req, res, next) {
Catalog.find(function(err, items){
if(err){
return next(err);
}
console.log(items);
res.json(items);
});
});
The model
var mongoose = require('mongoose');
var CatalogSchema = new mongoose.Schema({
title: String,
cost: String,
description: String
});
mongoose.model('Catalog', CatalogSchema);
The console.log gives me a [] but there is a collection named Catalog with the parameters filled.
If you have an existing collection, created outside of Mongoose, that you wish to query you need to configure your model to use that collection.
Otherwise, Mongoose will use a utility function to create a collection name by pluralizing and lowercasing the model name (which in your case would become catalogs).
To use Catalog as a collection, use the collection option:
var CatalogSchema = new mongoose.Schema({
title: String,
cost: String,
description: String
}, { collection : 'Catalog' });
you might check again the collection name in mongodb i.e whether it is 'Catalog' or 'Catalogs'. In my experience I found that if you name the model 'Catalog', then either mongoose creates the collection with a name 'Catalogs' if it is not already there or you have to create the collection first with a name 'Catalogs'.
You might check the following line also:
Catalog.find({}, function(err, items){
// your code goes here
}
Hope it helps..

nodeJS + MongoDB + Mongoose - Unable To Delete 1 Entry

I'm using nodeJS MongoDB/Mongoose to create/update/delete movies inside the database using Postman post/delete methods.
The create function is working fine, and even the remove function is working properly so when I use Postman I get the return: "Movie has been deleted!" like it should.
The only problem is that my function is emptying the entire database of movies instead of just that 1 movie, here is the remove function:
function destroy(req, res, next){
var movieID = req.body
Movie.remove(movieID, function(err,movie){
if(err){
res.status(400).send(err)
} else {
res.send("Movie has been deleted!")
db.close()
}
})
The movie object:
var movieSchema = new mongoose.Schema({
name: String,
yay: Number,
nay: Number,
release_date: Date,
in_theaters: Boolean,
released: Boolean,
buy_link: String,
imdb_link: String,
image_url: String,
description: String,
trailer_link: String
})
I want to delete a movie based on it's "name" so I only have to input the name and it will delete the entire movie.
Have you tried the findOneAndRemove query?
This query is much cleaner compared to finding a model and removing it inside the callback. Beside this I assume it's faster because you basically do 1 query instead of 2 after each other.
If you are passing direct value to Remove method, it will try to match with _id field.
As per your model, _id is ObjectId field which is managed automatically by mongodb.
In case if you enter like this. .remove("movie", callback) which is not a valid ObjectId.
Mongoose is discarding this invalid condition and executing Movie.remove({}); which is deleting all your records.
So it is better to validate whether the input is valid ObjectId or not before directly passing to Movie.remove();
I also recommend to use like this: Movie.remove({_id: movieId}, callback).
And for movie name :
Movie.remove({name: movieName}, callback);
Update:
You can take from Postman
var movieName = req.body.movieName;
Movie.remove({name: movieName}, function(err, updateObj){
});
Can you try this?
var movieName = req.body.name;
Movie.find('name': movieName, function(err, movie) {
if (err) res.send({error: err});
Movie.remove(function(err, movie){
if (err) res.send({error: err});
res.json({message: "Movie is removed", movie: movie});
});
});

Incorrect Subdocument Being Updated?

I've got a Schema with an array of subdocuments, I need to update just one of them. I do a findOne with the ID of the subdocument then cut down the response to just that subdocument at position 0 in the returned array.
No matter what I do, I can only get the first subdocument in the parent document to update, even when it should be the 2nd, 3rd, etc. Only the first gets updated no matter what. As far as I can tell it should be working, but I'm not a MongoDB or Mongoose expert, so I'm obviously wrong somewhere.
var template = req.params.template;
var page = req.params.page;
console.log('Template ID: ' + template);
db.Template.findOne({'pages._id': page}, {'pages.$': 1}, function (err, tmpl) {
console.log('Matched Template ID: ' + tmpl._id);
var pagePath = tmpl.pages[0].body;
if(req.body.file) {
tmpl.pages[0].background = req.body.filename;
tmpl.save(function (err, updTmpl) {
console.log(updTmpl);
if (err) console.log(err);
});
// db.Template.findOne(tmpl._id, function (err, tpl) {
// console.log('Additional Matched ID: ' + tmpl._id);
// console.log(tpl);
// tpl.pages[tmpl.pages[0].number].background = req.body.filename;
// tpl.save(function (err, updTmpl){
// if (err) console.log(err);
// });
// });
}
In the console, all of the ID's match up properly, and even when I return the updTmpl, it's saying that it's updated the proper record, even though its actually updated the first subdocument and not the one it's saying it has.
The schema just in case:
var envelopeSchema = new Schema({
background: String,
body: String
});
var pageSchema = new Schema({
background: String,
number: Number,
body: String
});
var templateSchema = new Schema({
name: { type: String, required: true, unique: true },
envelope: [envelopeSchema],
pagecount: Number,
pages: [pageSchema]
});
templateSchema.plugin(timestamps);
module.exports = mongoose.model("Template", templateSchema);
First, if you need req.body.file to be set in order for the update to execute I would recommend checking that before you run the query.
Also, is that a typo and req.body.file is supposed to be req.body.filename? I will assume it is for the example.
Additionally, and I have not done serious testing on this, but I believe your call will be more efficient if you specify your Template._id:
var template_id = req.params.template,
page_id = req.params.page;
if(req.body.filename){
db.Template.update({_id: template_id, 'pages._id': page_id},
{ $set: {'pages.$.background': req.body.filename} },
function(err, res){
if(err){
// err
} else {
// success
}
});
} else {
// return error / missing data
}
Mongoose doesn't understand documents returned with the positional projection operator. It always updates an array of subdocuments positionally, not by id. You may be interested in looking at the actual queries that mongoose is building - use mongoose.set('debug', true).
You'll have to either get the entire array, or build your own MongoDB query and go around mongoose. I would suggest the former; if pulling the entire array is going to cause performance issues, you're probably better off making each of the subdocuments a top-level document - documents that grow without bounds become problematic (at the very least because Mongo has a hard document size limit).
I'm not familiar with mongoose but the Mongo update query might be:
db.Template.update( { "pages._id": page }, { $set: { "pages.$.body" : body } } )

Trying to remove a subdocument in Mongoose gives me an internal mongoose error

I have a schema as follows (simplified):
var Permission = new Schema({
_id: String, // email address
role: String // "admin" or "member"
});
var Org = new Schema({
name: {type: String, index: {unique: true, dropDups: true}, trim: true},
permissions: [Permission]
});
An example document would look like this:
{
"name": "My Org",
"permissions" : [
{"_id" : "joe#gmail.com", "role" : "admin"},
{"_id" : "mary#gmail.com", "role" : "member"}
]
}
I am trying to delete one of the permissions rows, using the command org.permissions.remove(req.params.email), as shown in context below:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org
.findOne({name: name})
.select()
.exec(function(err, org) {
if (err) return Org.handleError(res, err);
if (!org) return Org.handleError(res, new Error("#notfound " + name));
org.permissions.remove(req.params.email);
org.save(function(err, org) {
if (err) return Org.handleError(res, err);
else return res.send(org);
});
});
};
When I do this, I get the following error:
TypeError: Cannot use 'in' operator to search for '_id' in joe#gmail.com
at EmbeddedDocument.Document._buildDoc (/../node_modules/mongoose/lib/document.js:162:27)
at EmbeddedDocument.Document (/../node_modules/mongoose/lib/document.js:67:20)
at EmbeddedDocument (/../node_modules/mongoose/lib/types/embedded.js:27:12)
at new EmbeddedDocument (/../node_modules/mongoose/lib/schema/documentarray.js:26:17)
at MongooseDocumentArray._cast (/../node_modules/mongoose/lib/types/documentarray.js:62:10)
at Object.map (native)
at MongooseDocumentArray.MongooseArray.remove (/../node_modules/mongoose/lib/types/array.js:360:21)
at model.Org.methods.removePermissions (/../models/org.js:159:20)
The only thing I can think of is that Mongoose does not support _id fields that are not ObjectID's? This is strange, because I use these elsewhere in my code and it works fine (e.g. org.permissions.id("joe#gmail.com") works).
Any suggestions much appreciated!
I'm not sure why using remove there isn't working, but you can do this atomically with findOneAndUpdate and the $pull operator:
exports.removePermissions = function(req, res) {
var name = req.params.name;
return Org.findOneAndUpdate(
{name: name},
{$pull: {permissions: {_id: req.params.email}}},
function(err, org) {
// org contains the updated doc
...
});
};
As per this answer, you need to call remove() on the subdocument you want to remove, rather than on the entire subdocument array.
So, change:
org.permissions.remove(req.params.email);
to:
org.permissions.id(req.params.email).remove();
This two-step method has the added advantage over the answer supplied by #JohnnyHK in that you can validate whether the subdocument actually exists before removing it. This can be useful if you'd like to send a 404 response indicating that the subdocument doesn't exist - as far as I am aware, this isn't possible using the $pull atomic operator.
Note that this also will only work if your subdocument array has a schema, as illustrated in the question. If it doesn't, or it has a schema type of Mixed, the collection returned from the database will be a plain array rather than a Mongoose-enhanced array. This means that there is no .id() function. In this case, I would use lodash#remove instead:
_.remove(org.permissions, (function(permission) {
return permission._id.toString() === req.params.email;
}));

Resources