Mongoose .populate not showing in JSON - node.js

I have some code which populates a section called "user". When I console.log the result I get a nicely filled document like this:
[ { _id: 60f92778ceefc423d834d9c0,
title: 'Vaatwasser',
day: 0,
house: '60f455b1e0394e12a226045a',
user: { _id: 60f4142ff55f81251a7224ff, name: 'Lucas Blommers' },
createdAt: 2021-07-22T08:08:24.477Z,
updatedAt: 2021-07-22T08:08:24.477Z,
__v: 0 },
{ _id: 60f92788ceefc423d834d9c3,
title: 'Koken',
day: 0,
house: '60f455b1e0394e12a226045a',
user: { _id: 60f4142ff55f81251a7224ff, name: 'Lucas Blommers' },
createdAt: 2021-07-22T08:08:40.712Z,
updatedAt: 2021-07-22T08:08:40.712Z,
__v: 0 } ]
But when I request the data using Android or Postman I get the following result:
{
"_id": "60f92778ceefc423d834d9c0",
"title": "Vaatwasser",
"day": 0,
"house": "60f455b1e0394e12a226045a",
"user": {},
"createdAt": "2021-07-22T08:08:24.477Z",
"updatedAt": "2021-07-22T08:08:24.477Z",
"__v": 0
},
As you can see the user is an empty object after it gets send.
Is this a bug in the JSON library or am I doing something wrong on the server.
-Edit-
Here's the server side code:
const housekeepings = await Housekeeping.find({day:day, house:
house}).populate("user", "name")
console.log(housekeepings)
return res.send(housekeepings)

"You can try await Housekeeping.find({day:day, house: house}).populate("user", "name").lean() – Cuong Le Ngoc"
This is the answer. Thank you very much Cuong.

Related

Mongoose nested select not working with populate - fields are still showing when they've been selected out

I have a Post, User, Comment section with the following schemas:
postSchema = mongoose.Schema({
post_title,
post_description,
post_timestamp,
post_author:{
type:mongoose.Schema.Types.ObjectId,
ref: "User"
},
post_likes:{
type: [userSchema.schema]
},
post_comments:{
type: [commentSchema.schema]
}
)}
userSchema = mongoose.Schema({
username,
email,
password,
date
})
commentSchema = mongoose.Schema({
comment_description,
comment_timestamp,
comment_author:{
type:mongoose.Schema.Types.ObjectId,
ref: "User",
require:true,
}
})
The format that I want my output is the following:
{
"_id": "638bdf38b2ad86abe32455fd",
"post_title": "This is a test post",
"post_description": "abc...",
"post_timestamp": "2022-12-03T23:41:04.292Z",
"post_author": {
"username": "test10"
},
"post_comments": [
{
"comment_description": "This is my first comment for this new post",
"comment_timestamp": "2022-12-03T23:51:24.306Z",
"comment_author": {
"username": "test10"
}
},
{
"comment_description": "This is my first comment for this new post",
"comment_timestamp": "2022-12-04T10:06:22.091Z",
"comment_author": {
"username": "test11"
}
}
]
}
i.e. for the post_comments I would like the _id and __v removed.
I'm receiving my post documents/objects from my server in the following format:
{
"_id": "638bdf38b2ad86abe32455fd",
"post_title": "This is a test post",
"post_description": "abc...",
"post_timestamp": "2022-12-03T23:41:04.292Z",
"post_author": {
"username": "test10"
},
"post_comments": [
{
"comment_description": "This is my first comment for this new post",
"comment_timestamp": "2022-12-03T23:51:24.306Z",
"comment_author": {
"username": "test10"
},
"_id": "638be126b9fe7d1b4823882c",
"__v": 0
},
{
"comment_description": "This is my first comment for this new post",
"comment_timestamp": "2022-12-04T10:06:22.091Z",
"comment_author": {
"username": "test11"
},
"_id": "638c738ab63be98a89d2cb76",
"__v": 0
}
]
}
This is my code to get the data in my app.js route
const posts = await Post.find({}, {
post_title: 1,
post_description: 1,
post_timestamp: 1
})
.populate({
path: "post_author",
model: "User",
select: "-_id username",
})
.populate({
path: "post_comments",
model: "Comment",
select: "-_id -__v comment_description comment_timestamp", // <-- This doesn't work
populate: {
path: "comment_author",
model: "User",
select: "-_id:0 username"
}
})
My select value doesn't make a difference for the post_comments bit. I could use it as is, but I'd like to understand why it's not working. I'm overlooking something but I'm not sure what it is.
I've tried writing the select in other ways, for example:
.populate({
path: "post_comments",
model: "Comment",
_id: 0,
__v: 0,
// select: "-_id -__v comment_description comment_timestamp",
populate: {
path: "comment_author",
model: "User",
select: "-_id username"
}
})
and nothing I do seems to work
I've also tried the solution in this question: Mongoose select with populate not working but that hasn't worked either.
Doing:
.populate("post_comments", "comment_description")
Gives me the following output:
...
"post_comments": [
{
"comment_description": "This is my first comment for this new post",
"comment_timestamp": "2022-12-03T23:51:24.306Z",
"comment_author": "63697530dbf87061fa4c1665",
"_id": "638be126b9fe7d1b4823882c",
"__v": 0
},
...

user.email become undefined in Node.js mongo

Our backend was deployed on Heroku and running without error until today but 2-3 hours ago our servers started to give crash, and the reason is our function below can't read property 'email' of user and prints undefined. No idea why this is happening suddenly. First, I thought it was related to the server but I'm getting the same result in localhost too. As i said before code was working literally 2-3 hours ago.
findMe function
exports.findMe = (req, res) => {
User.findOne(
{ _id: req.userData.userId },
{ email: 0, password: 0 },
(err, user) => {
if (err) {
return res.status(500).json({
...err,
});
}
console.log(user.email);
const expires = "10y";
const token = jwt.sign(
{
email: user.email,
userId: user._id,
username: user.username,
committeeId: user.committeeId,
role: user.role,
},
process.env.JWT_KEY,
{
expiresIn: expires,
}
);
return res.status(200).json({
user: user,
token: token,
});
}
);
};
Output of code above
{
"user": {
"education": {
"university": "YILDIZ TEKNİK ÜNİVERSİTESİ",
"department": "MATEMATİK MÜHENDİSLİĞİ",
"year": 3
},
"oldCommittees": {
"committeeId": null,
"title": null,
"year": null,
"date": "2021-08-28T21:53:48.992Z"
},
"isVerified": true,
"bio": null,
"photo": null,
"photoSm": null,
"photoXs": null,
"phoneNo": null,
"committeeId": "5d9360e99b572100172cc581",
"title": "CS Başkanı",
"role": 0,
"blockedUsers": [],
"_id": "5d9362ec9b572100172cc648",
"name": "Emir",
"surname": "Kutlugün",
"username": "emir-kutlugun2",
"date": "2019-10-01T14:30:04.884Z",
"__v": 0
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZDkzNjJlYzliNTcyMTAwMTcyY2M2NDgiLCJ1c2VybmFtZSI6ImVtaXIta3V0bHVndW4yIiwiY29tbWl0dGVlSWQiOiI1ZDkzNjBlOTliNTcyMTAwMTcyY2M1ODEiLCJyb2xlIjowLCJpYXQiOjE2MzAxODc2MzEsImV4cCI6MTk0NTc2MzYzMX0.Jjl_-mXN2Ozn96yJpqNPYPFl3mngnZ4N_I8KYBNYCoo"
}
console.log(user.email) logs undefined
This code:
User.findOne(
{ _id: req.userData.userId },
{ email: 0, password: 0 },
Uses mongoose's Model.findOne() method. The first parameter, { _id: req.userData.userId } is a document containing the conditions you want the document returned to match.
The second parameter, { email: 0, password: 0 } is the projection. This tells MongoDB which fields should be included in the result. The projection in your code is an example of returning all fields but excluded fields. The document you get back from this operation will have every field except email and password.
If you want to include email in the result for later use, you will need to remove it from this projection. You could change the projection to { password: 0 }, so the email field is included and the password field is still excluded.
Put together, it may look like this:
User.findOne(
{ _id: req.userData.userId },
{ password: 0 },

Mongoose populate 3 deep nested schema with JSON response

I have a find() query that when executed, I can see the json with the nested schemas that I want to see except for the 'artista' attribute only displays the id, instead of the properties I want. See below:
{
"total": 1,
"ordenes": [
{
"artpieces": [
{
"_id": "60c1388f30316c02b9f6351f",
"artista": "60c055736c7ca511055a0e1a",
"nombre": "LILIES"
},
{
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"artista": "60c136bf30316c02b9f6351b"
}
],
"_id": "60c414f9ea108a14ef75a9fb",
"precio": 3000,
"usuario": {
"_id": "609c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
The query I use to get the json above:
const [total, ordenes] = await Promise.all([
Orden.countDocuments(),
Orden.find()
.populate("usuario", "nombre")
.populate("artpieces", ["nombre","artista","nombre"])
]);
res.json({
total,
ordenes
});
It's an order schema that has artpieces. Each artpiece (I called it 'producto'), has a name a genre, an author/artist and the user which the order belongs to.
my Schema for the orden.js:
const { Schema, model } = require('mongoose');
const OrdenSchema = Schema({
artpieces: [
{
type: Schema.Types.ObjectId,
required: true,
ref: 'Producto'
}
],
estado: {
type: Boolean,
default: true,
required: true
},
usuario: {
type: Schema.Types.ObjectId,
ref: 'Usuario',
required: true
},
precio: {
type: Number,
required: true
}
})
OrdenSchema.methods.toJSON = function () {
const { __v, estado, ...data} = this.toObject();
return data;
}
module.exports = model('Orden', OrdenSchema);
Last thing I want to mention, I know for a fact that I have the code necessary in the artista.js model to display the name of the artist because I have a similar query to display all the artpieces with each artpiece have a genre and an artist.
That example looks like so (to give context):
{
"total": 4,
"productos": [
{
"precio": 0,
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"categoria": {
"_id": "60c04e3605d3c10ed10389e4",
"nombre": "NEO CUBISMO"
},
"artista": {
"_id": "60c136bf30316c02b9f6351b",
"nombre": "PICASSO"
},
"usuario": {
"_id": "609c8c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
What am I doing wrong that I can't get my json result at the top look like the json at the bottom, where the artist attribute is?
Also just to point out, I have checked how to nest populate methods in order SO posts including the path and the ref to the Schema and still haven't been able to get the expected result.

Mongoose Find not filtering for object property inside array (MongoDB 4.4.1)

I've been trying to make a chat app and I use the following schema for messages:
const messageObject = {
sender: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
message: {type: String, require: true, min: 1, max: global.messageMaxLength},
time: Number,
seen: Boolean
}
const MessageSchema = mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
messages: [messageObject]
}, {versionKey: false}) ;
module.exports = mongoose.model('Messages', MessageSchema);
It takes in entries successfully. Example of entries:
"_id": "5fb3d971e6092d2da001bbad",
"messages": [
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
]
}
I want only the seen:false messages now, but when I try to code that in find, or aggregate, I get all the data, ie, the above records:
MessageModel.find({$and: [{_id: req.userData.userid}, {'messages.seen': false}]}).then(result => {
res.status(200).json({
message: "passed",
result,
});
})
It however works fine and returns [] if I give {"messages.seen": null}}, and 1 entry with seen: null will return an entire array.
Ive seen all forums there's no place where anybody has encountered this kind of error. Please help.
Thanks
Your message objects are nested inside a document and Mongo will only return either the full document or specific top level fields you have projected. Mongo will not filter out objects within a nested array (as is the case with your schema). So if any of the message objects within the array match the selector, the whole document itself passes the match and returns as a result.
Your options are to either:
Filter out the true/false messages within your code
Change your db structure so that you have a separate messages collection where each document is a single message. So in your example, the collection would look like:
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
And then you can query that collection with:
{ "parentId": "5fb3d971e6092d2da001bbad","seen": false}

Referance multiple documents Mongoose

I'm trying to connect 3 different documents using mongoose (mainly to learn how to use it), I've set up 3 different schemas as follows:
(all of them are in there own files)
const Books = new Schema({
title: { type: String, required: true, unique: true },
description: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'Authors' },
stores: [{
available: Number,
store: { type: mongoose.Schema.Types.ObjectId, ref: 'Stores' }
}]
});
exports.Books = mongoose.model('Books', Books);
const Authors = new Schema({
name: { type: String, required: true },
description: String,
born: Date,
died: { type: Date, default: null }
});
exports.Authors = mongoose.model('Authors', Authors);
const Stores = new Schema({
name: { type: String, required: true, unique: true },
description: String,
address: String
});
exports.Stores = mongoose.model('Stores', Stores);
and then I add the following data:
author.name = 'Jonathan Swift';
author.description = 'old satiric bloke';
author.born = new Date(1667, 10, 30);
author.died = new Date(1745, 9, 19);
author.save()
.catch((err) => console.log(err))
.then(() => console.log('author saved'));
store.name = 'Book shop 1';
store.description = 'This is where the cool kids buy all there books!';
store.address = 'La La Land, 123 Combaja street';
store.save()
.catch((err) => console.log(err))
.then(() => console.log('store saved'));
book.title = 'gullivers travels';
book.author = '58a79345711f2350999e0911'; // _id from the author save
book.description = 'A book about a big guy in a small world';
book.stores = [{
available: 8,
store: '58a79345711f2350999e0912' // _id from the store save
}];
book.save()
.catch((err) => console.log(err))
.then(() => console.log('store saved'));
The problem I find is that when I run book.find() it returns:
[
{
"_id": "58a795b8298b50527412815c",
"description": "A book about a big guy in a small world",
"author": {
"_id" : "58a79345711f2350999e0911",
"born" : -9532947600000,
"died" : -7075130400000,
"description" : "old satiric bloke",
"name" : "Jonathan Swift",
"__v" : 0
},
"title": "gullivers travels",
"__v": 0,
"stores": [
{
"available": 8,
"store": "58a79345711f2350999e0912",
"_id": "58a795b8298b50527412815d"
}
]
}
]
what I was expecting/hoping for was to get the entire store as well the same way as with the author, have I missed something or how should I go about this to achieve the expected result?
I've tried populate but without success
In your Books model author & stores.store are reference, you shouldn't have author field populated if you don't use populate().
If you specify directly the _id of your store & author you have just created :
var author = new Authors({
name: 'Jonathan Swift',
description: 'old satiric bloke',
born: new Date(1667, 10, 30),
died: new Date(1745, 9, 19)
});
author.save();
var store = new Stores({
name: 'Book shop 1',
description: 'This is where the cool kids buy all there books!',
address: 'La La Land, 123 Combaja street'
});
store.save();
var book = new Books({
title: 'gullivers travels',
author: author._id, // _id from the author save
description: 'A book about a big guy in a small world',
stores: [{
available: 8,
store: store._id // _id from the store save
}]
})
book.save();
Books.find() gives the expected result :
[
{
"_id": "58a7a2529a8b894656a42e00",
"title": "gullivers travels",
"author": "58a7a2529a8b894656a42dfe",
"description": "A book about a big guy in a small world",
"__v": 0,
"stores": [
{
"available": 8,
"store": "58a7a2529a8b894656a42dff",
"_id": "58a7a2529a8b894656a42e01"
}
]
}
]
If you want stores.store & author populated, use :
Books.find().populate([{path:'author'},{path:'stores.store'}]).exec(function(err, res) {
console.log(JSON.stringify(res, null, 2));
});
It gives as expected author & stores.store both populated :
[
{
"_id": "58a7a2529a8b894656a42e00",
"title": "gullivers travels",
"author": {
"_id": "58a7a2529a8b894656a42dfe",
"name": "Jonathan Swift",
"description": "old satiric bloke",
"born": "1667-11-29T23:00:00.000Z",
"__v": 0,
"died": "1745-10-18T22:00:00.000Z"
},
"description": "A book about a big guy in a small world",
"__v": 0,
"stores": [
{
"available": 8,
"store": {
"_id": "58a7a2529a8b894656a42dff",
"name": "Book shop 1",
"description": "This is where the cool kids buy all there books!",
"address": "La La Land, 123 Combaja street",
"__v": 0
},
"_id": "58a7a2529a8b894656a42e01"
}
]
}
]

Resources