Mongoose findbyid / find({id: "..."}) returning Null - mongodb

I have seen this asked several times, but I haven't found a solution that has worked. I am trying to query a MongoDB using Mongoose .findById and am not getting the expected results. find({id: "..."}) is also not returning anything.
Using find without any parameters displays all of the expected results including id key-value pairs, but I cannot use the id value in the query.
Using mongoose: 5.4.9
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongo-exercises', { useNewUrlParser: true });
const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [String],
date: { type: Date, default: Date.now },
isPublished: Boolean,
price: Number
});
const Course = mongoose.model('Course', courseSchema);
async function getCourses() {
return await Course
.find()
}
async function run() {
const result = await getCourses();
console.log(result);
}
run();
//Return
[ { tags: [ 'react', 'frontend' ],
_id: 5a68fdf95db93f6477053ddd,
date: 2018-01-24T21:43:21.589Z,
name: 'React Course',
author: 'Mosh',
isPublished: false,
__v: 0 },
{ tags: [ 'aspnet', 'backend' ],
_id: 5a68fde3f09ad7646ddec17e,
date: 2018-01-24T21:42:59.605Z,
name: 'ASP.NET MVC Course',
author: 'Mosh',
isPublished: true,
price: 15,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68fe2142ae6a6482c4c9cb,
date: 2018-01-24T21:44:01.075Z,
name: 'Node.js Course by Jack',
author: 'Jack',
isPublished: true,
price: 12,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68fdd7bee8ea64649c2777,
date: 2018-01-24T21:42:47.912Z,
name: 'Node.js Course',
author: 'Mosh',
isPublished: true,
price: 20,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68ff090c553064a218a547,
date: 2018-01-24T21:47:53.128Z,
name: 'Node.js Course by Mary',
author: 'Mary',
isPublished: false,
price: 12,
__v: 0 },
{ tags: [ 'angular', 'frontend' ],
_id: 5a6900fff467be65019a9001,
date: 2018-01-24T21:56:15.353Z,
name: 'Angular Course',
author: 'Mosh',
isPublished: true,
price: 15,
__v: 0 },
{ tags: [ 'express', 'backend' ],
_id: 5a68fdc3615eda645bc6bdec,
date: 2018-01-24T21:42:27.388Z,
name: 'Express.js Course',
author: 'Mosh',
isPublished: true,
price: 10,
__v: 0 } ]
That code verifies I'm connected to the correct database and retrieving real ids. When I modify the getCourses function as shown below, I get null or an empty array depending on whether I use findById or find({id: "..."}).
async function getCourses() {
return await Course
.findById('5a68fdf95db93f6477053ddd')
}
//null
async function getCourses() {
return await Course
.find({ id: '5a68fdf95db93f6477053ddd' })
}
// []
async function getCourses() {
return await Course
.find({ _id: '5a68fdf95db93f6477053ddd' })
}
// []
Any help would be greatly appreciated. Thank you!
EDIT: Showing full find() response.

Upon our discussion as your importing the data from JSON file, So its inserting the _id as string, While finding the document by _id, moognoose automatically converting string to Object.
// Original JSON
{
"_id": "5a68fde3f09ad7646ddec17e",
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}
Solution 1 :
To insert the _id as in the JSON, change your _id field with $oid as below,
{
"_id": { "$oid": "5a68fde3f09ad7646ddec17e" },
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}
Solution 2 :
Remove _id from your JSON, MongoDB will generate a _id automatically
{
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}

Related

How to query to get specific objects from array of objects?

Currently I am learning mongodb. Suppose I have one collection named post in mongodb which data is :
[{
id: 123,
uId: 111,
msg: 'test 1',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false},
{name: 'attach2', url: 'https://example.com', isDeleted: true}
]
},
{
id: 456,
uId: 222,
msg: 'test 2',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: true}
]
},
{
id: 789,
uId: 333,
msg: 'test 3',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
}]
Now i want the result of all post where attachments.isDeleted is false which look like :
[{
id: 123,
uId: 111,
msg: 'test 1',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
},
{
id: 456,
uId: 222,
msg: 'test 2',
attachments: []
},
{
id: 789,
uId: 333,
msg: 'test 3',
attachments: [
{name: 'attach1', url: 'https://example.com', isDeleted: false}
]
}]
I tried this db.post.find({attachments: {$elemMatch: {isDeleted: false}}}) but it is not working. I have taken help from [https://stackoverflow.com/questions/62953855/how-get-query-for-array-of-objects-in-mongodb]
I think this is what you are looking for. It uses the Mongodb aggregation framework. You can take a look the documentation to see details. In brief, the $project stage allow us select fields to show or to hide, and it admits calculated fields. We calculated a new attachments field using $filter stage with a the given condition (isDeleted equals to false).
db.collection.aggregate([
{
"$project": {
_id: 1,
uId: 1,
msg: 1,
attachments: {
"$filter": {
"input": "$attachments",
"as": "attachment",
"cond": {
"$eq": [
"$$attachment.isDeleted",
false
]
}
}
}
}
}
])
You can try it in: https://mongoplayground.net/p/YDvpkbZIZ2H
Note that I changed id by _id, but it was only for style purpose.
Hope I helped.

Populate data in mongoose on collections(generated by strapi.io)

I have 3 collections in mongodb generated by strapi.io, I want customize query populate data by mongoose in my project. But i cannot populate data like strapi result.
Category collection:
{
_id: "5d10a731c5077836540bebf0",
posts: [
"5d10a6fbc5077836540bebed",
"5d10af45c5077836540bebf1"
],
name: "Support",
ids: "support",
keywords: "support",
des: "support des",
createdAt: "2019-06-24T10:34:25.383Z",
updatedAt: "2019-06-24T11:08:53.475Z",
__v: 0,
id: "5d10a731c5077836540bebf0"
}
Post collection:
{
_id: "5d10af45c5077836540bebf1",
ids: "test",
title: "this is test",
des: "test",
keywords: "test",
body: "testtesttesttesttest",
createdAt: "2019-06-24T11:08:53.467Z",
updatedAt: "2019-06-24T11:08:53.477Z",
__v: 0,
id: "5d10af45c5077836540bebf1"
}
FileUpload collection:
{
_id: "5d10af45c5077836540bebf2",
name: "THUMBNAIL.png",
sha256: "0bP9PI3R_ygF07cLhg5U-syLeVCz4ZbBrwZZi2OtPL4",
hash: "9f4e8bbd13b94f2baa3d26b335124717",
ext: ".png",
mime: "image/png",
size: "297.33",
url: "/uploads/9f4e8bbd13b94f2baa3d26b335124717.png",
provider: "local",
related:
[
{
_id: "5d10af45c5077836540bebf3",
ref: "5d10af45c5077836540bebf1",
kind: "Post",
field: "pic"
}
],
createdAt: "2019-06-24T11:08:53.501Z",
updatedAt: "2019-06-24T11:08:53.505Z",
__v: 0,
id: "5d10af45c5077836540bebf2"
}
I can populate "posts" on Category model with
Cat.find().populate({path: 'posts',model: 'Post'})
But I can not populate "categories" and "pic" on Post model.
How can I populate "categories" and "pic" like this result of strapi:
{
_id: "5d10af45c5077836540bebf1",
ids: "test",
title: "this is test",
des: "test",
keywords: "test",
body: "testtesttesttesttest",
createdAt: "2019-06-24T11:08:53.467Z",
updatedAt: "2019-06-24T11:08:53.477Z",
__v: 0,
id: "5d10af45c5077836540bebf1",
pic: {
_id: "5d10af45c5077836540bebf2",
name: "THUMBNAIL.png",
sha256: "0bP9PI3R_ygF07cLhg5U-syLeVCz4ZbBrwZZi2OtPL4",
hash: "9f4e8bbd13b94f2baa3d26b335124717",
ext: ".png",
mime: "image/png",
size: "297.33",
url: "/uploads/9f4e8bbd13b94f2baa3d26b335124717.png",
provider: "local",
related: [
"5d10af45c5077836540bebf1"
],
createdAt: "2019-06-24T11:08:53.501Z",
updatedAt: "2019-06-24T11:08:53.505Z",
__v: 0,
id: "5d10af45c5077836540bebf2"
},
categories:
[
{
posts: [
"5d10a6fbc5077836540bebed",
"5d10af45c5077836540bebf1"
],
_id: "5d10a731c5077836540bebf0",
name: "Support",
ids: "support",
keywords: "support",
des: "support des",
createdAt: "2019-06-24T10:34:25.383Z",
updatedAt: "2019-06-24T11:08:53.475Z",
__v: 0,
id: "5d10a731c5077836540bebf0"
}
]
}
you have to create another collection inside the post. as like you have an independent collection then you can populate. for example :
Post: {body: ,title: ,Image : { image: {name : , etc }}}
this can be a collection inside collection. the above code is not correct you have to mention your own fields.

Multiple array elements are not updating in mongodb [duplicate]

I need to modify a document inside an array that is inside another array.
I know MongoDB doesn't support multiple '$' to iterate on multiple arrays at the same time, but they introduced arrayFilters for that.
See: https://jira.mongodb.org/browse/SERVER-831
MongoDB's sample code:
db.coll.update({}, {$set: {“a.$[i].c.$[j].d”: 2}}, {arrayFilters: [{“i.b”: 0}, {“j.d”: 0}]})
Input: {a: [{b: 0, c: [{d: 0}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Output: {a: [{b: 0, c: [{d: 2}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Here's how the documents are set:
{
"_id" : ObjectId("5a05a8b7e0ce3444f8ec5bd7"),
"name" : "support",
"contactTypes" : {
"nonWorkingHours" : [],
"workingHours" : []
},
"workingDays" : [],
"people" : [
{
"enabled" : true,
"level" : "1",
"name" : "Someone",
"_id" : ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"contacts" : [
{
"_id" : ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries" : "1",
"priority" : "1",
"type" : "email",
"data" : "some.email#email.com"
}
]
}
],
"__v" : 0
}
Here's the schema:
const ContactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const PersonSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [ContactSchema]
});
const GroupSchema = new Schema({
name: String,
people: [PersonSchema],
workingHours: { start: String, end: String },
workingDays: [Number],
contactTypes: { workingHours: [String], nonWorkingHours: [String] }
});
I need to update a contact. This is what I tried using arrayFilters:
Group.update(
{},
{'$set': {'people.$[i].contacts.$[j].data': 'new data'}},
{arrayFilters: [
{'i._id': mongoose.Types.ObjectId(req.params.personId)},
{'j._id': mongoose.Types.ObjectId(req.params.contactId)}]},
function(err, doc) {
if (err) {
res.status(500).send(err);
}
res.send(doc);
}
);
The document is never updated and I get this response:
{
"ok": 0,
"n": 0,
"nModified": 0
}
What am I doing wrong?
So the arrayFilters option with positional filtered $[<identifier>] does actually work properly with the development release series since MongoDB 3.5.12 and also in the current release candidates for the MongoDB 3.6 series, where this will actually be officially released. The only problem is of course is that the "drivers" in use have not actually caught up to this yet.
Re-iterating the same content I have already placed on Updating a Nested Array with MongoDB:
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
All this means is that the current "driver" implementation of .update() actually "removes" the necessary arguments with the definition of arrayFilters. For NodeJS this will be addressed in the 3.x release series of the driver, and of course "mongoose" will then likely take some time after that release to implement it's own dependencies on the updated driver, which would then no longer "strip" such actions.
You can however still run this on a supported server instance, by dropping back to the basic "update command" syntax usage, since this bypassed the implemented driver method:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = mongoose.Types.ObjectId;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const contactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const personSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [contactSchema]
});
const groupSchema = new Schema({
name: String,
people: [personSchema],
workingHours: { start: String, end: String },
workingDays: { type: [Number], default: undefined },
contactTypes: {
workingHours: { type: [String], default: undefined },
contactTypes: { type: [String], default: undefined }
}
});
const Group = mongoose.model('Group', groupSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.remove() )
);
// Create sample
await Group.create({
name: "support",
people: [
{
"_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com"
},
{
"_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries": "1",
"priority": "1",
"type": "email",
"data": "some.email#example.com"
}
]
}
]
});
let result = await conn.db.command({
"update": Group.collection.name,
"updates": [
{
"q": {},
"u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
"multi": true,
"arrayFilters": [
{ "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
{ "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
]
}
]
});
log(result);
let group = await Group.findOne();
log(group);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Since that sends the "command" directly through to the server, we see the expected update does in fact take place:
Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: 'adifferent.email#example.com', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: 'some.email#example.com' } ] } ], __v: 0 })
{ n: 1,
nModified: 1,
opTime:
{ ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
t: 24 },
electionId: 7fffffff0000000000000018,
ok: 1,
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
'$clusterTime':
{ clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
"_id": "5a06557fb568aa0ad793c5e4",
"name": "support",
"__v": 0,
"people": [
{
"_id": "5a05a8c3e0ce3444f8ec5bd8",
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com",
"_id": "5a06557fb568aa0ad793c5e5"
},
{
"_id": "5a05a8dee0ce3444f8ec5bda",
"retries": "1",
"priority": "1",
"type": "email",
"data": "new data" // <-- updated here
}
]
}
]
}
So right "now"[1] the drivers available "off the shelf" don't actually implement .update() or it's other implementing counterparts in a way that is compatible with actually passing through the necessary arrayFilters argument. So if you are "playing with" a development series or release candiate server, then you really should be prepared to be working with the "bleeding edge" and unreleased drivers as well.
But you can actually do this as demonstrated in any driver, in the correct form where the command being issued is not going to be altered.
[1] As of writing on November 11th 2017 there is no "official" release of MongoDB or the supported drivers that actually implement this. Production usage should be based on official releases of the server and supported drivers only.
I had a similar use case. But my second level nested array doesn't have a key. While most examples out there showcase an example with arrays having a key like this:
{
"id": 1,
"items": [
{
"name": "Product 1",
"colors": ["yellow", "blue", "black"]
}
]
}
My use case is like this, without the key:
{
"colors": [
["yellow"],
["blue"],
["black"]
]
}
I managed to use the arrayfilters by ommiting the label of the first level of the array nest. Example document:
db.createCollection('ProductFlow')
db.ProductFlow.insertOne(
{
"steps": [
[
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Livestock Truck",
"type": "road",
"thirdParty": true
}
}
},
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Airplane",
"type": "air",
"thirdParty": true
}
}
}
],
[
{
"actionType": "store",
"payload": {
"company": "Company A",
"is_supplier": false
}
}
],
[
{
"actionType": "sell",
"payload": {
"reseller": "Company B",
"is_supplier": false
}
}
]
]
}
)
In my case, I want to:
Find all documents that have any steps with payload.vehicle.thirdParty=true and actionType=dispatch
Update the actions set payload.vehicle.thirdParty=true only for the actions that have actionType=dispatch.
My first approach was withour arrayfilters. But it would create the property payload.vehicle.thirdParty=true inside the steps with actionType store and sell.
The final query that updated the properties only inside the steps with actionType=dispatch:
Mongo Shell:
db.ProductFlow.updateMany(
{"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": true, "actionType": "dispatch"}}}},
{"$set": {"steps.$[].$[i].payload.vehicle.thirdParty": false}},
{"arrayFilters": [ { "i.actionType": "dispatch" } ], multi: true}
)
PyMongo:
query = {
"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": True, "actionType": "dispatch"}}}
}
update_statement = {
"$set": {
"steps.$[].$[i].payload.vehicle.thirdParty": False
}
}
array_filters = [
{ "i.actionType": "dispatch" }
]
NOTE that I'm omitting the label on the first array at the update statement steps.$[].$[i].payload.vehicle.thirdParty. Most examples out there will use both labels because their objects have a key for the array. I took me some time to figure that out.

Mongoose $push add to document

I'm trying to add an element to an existing array, but it produces an error:
The field 'data' must be an array but is of type object in document
Scheme:
const testScheme = new Schema({
user: {
type: String,
required: true
},
data: [{
platform: {
type: String,
required: true
},
item_name: {
type: String,
required: true
},
price: {
type: Number,
default: 0
},
updatedAt: Date
}]
}, {
versionKey: false,
timestamps: true
});
Document in mongodb:
"data": [{
"price": 50,
"_id": "5a84268d6c78a60c10479437",
"platform": "pl1",
"item_name": "test"
}],
"_id": "5a841bccb44cb8cd5b974d71",
"user": "Ivan",
"updatedAt": "2018-02-14T12:07:41.793Z",
"createdAt": "2018-02-14T11:21:48.104Z"
Query:
var item = {
"platform": "pl700",
"item_name": "someText",
"price": 700,
"updatedAt": new Date()
};
Data.findOneAndUpdate({
'user': 'Ivan'
}, {
$push: {
'data': item
}
}, {
safe: true,
upsert: true
},
function(err, data) {
if (err) return res.status(500).send({
'error': err
});
res.status(200).send({
'data': data
});
}
);
I trying query with $set parametr and it works, but $push, $addToSet didn't work for me. Also i tried to google this problem and can't solve it.
It is not clear what you are intending to do.
To push an item into array you use $addToSet/$push. For updating a array you use $set.
Using $set you can update the whole document or you can update the specific field.
Update whole doc
Data.findOneAndUpdate({
'user': 'Ivan',
'data._id':item._id
}, {
$set: {
'data.$': item
}
}...
)
Update specific field
Data.findOneAndUpdate({
'user': 'Ivan',
'data._id':item._id
}, {
$set: {
'data.$.price': item.price
}
}...
)

How to populate multilevel array in mongoose

I have a Quiz Model with array of Section and each Sections has array of reference to Question.
I want to populate Quiz with question.
QuizModel
var quizSchema = new Schema({
name: String,
code: Number,
creator: String,
createdBy: { type: Schema.Types.ObjectId, ref: 'user' },
section:[{
title: String,
questions: [{type: Schema.Types.ObjectId ,ref: 'question'}]
}],
createdOn: {type:Date, default:Date.now}
});
and questionModel
var questionSchema = new mongoose.Schema(
{
question: String,
answer: [String],
correct: Number,
createdOn: {type:Date, default:Date.now}
});
I have following the official mongoose documentation for populating
http://mongoosejs.com/docs/populate.html#deep-populate
My attempt
quizSchema.find({_id: quiz_id})
.populate({
path: 'section' ,
populate: {
path: 'section.questions',
model: 'questionSchema'
}
})
.exec( function (err, result) {
if (err) return done(err,null);
console.log("list of questions are" + result);
return done(err, result);
});
}
The output I am getting is list of question's id not the actual question.
{
"status": "success",
"message": "Quiz data",
"result": [
{
"_id": "57fd5912ec0ad6bc8b67d71c",
"name": "My Quiz",
"creator": "foo",
"__v": 0,
"code": 124,
"createdOn": "2016-10-11T21:26:42.774Z",
"section": [
{
"_id": "57fd7e82c20a2fe5da3ed569",
"questions": [
"57fd7f8560e98fe710878820",
"57fd7f9d60e98fe710878821",
"57fd81408b20dae9108d347c",
"57fd81408b20dae9108d347d",
"57fd826aea5159ea5ff2f1a9",
"57fd82ab0dbc0feaa753e50c",
"57fd82efd789afeb0353f036",
"57fd84b0fef6a2ed21fad5ae",
"57fd84cc5dab10ed471bcaf5",
"57fd84cd5dab10ed471bcaf6"
]
},
{
"title": "next section",
"_id": "57fff1e0f1913138c27e50a0",
"questions": [
"57fff242f1913138c27e50a1"
]
}
]
}
]
}
I think I am doing something wrong with populate field, but not sure .
Just alter your query to
quizSchema.find({_id: quiz_id})
.populate({
path: 'section.questions'
})