How to do multilevel populate in mongoose with nodejs - node.js

I would like to create the data for blog post with list of posts, its associated comments with replies along with user details.
I tried nested populate but could do for only one level. Like I get the comments object and in this I need to populate replies and userId. Inside replies I need to populate userId and repliesCount. In this, only the populate that is given at last returns the populated data other fields list only the Ids.
posts.model.js:
const mongoose = require('mongoose')
var ObjectId = mongoose.Schema.Types.ObjectId
let PostsSchema = new mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
celebrityId: {
type: ObjectId,
ref: 'CelebrityDetails'
},
type: {
type: Number,
default: 1
},
isForwarded: {
type: Number,
default: 0
},
originalPostId: {
type: ObjectId,
default: null
},
title: {
type: String
},
description: {
type: String
},
status: {
type: Number,
default: 1
}
}, {
timestamps: true,
collection: 'fan_posts'
})
PostsSchema.virtual('isMyPost', {
ref: 'User',
localField: 'userId',
foreignField: '_id',
count: true
})
PostsSchema.virtual('comments', {
ref: 'PostComment',
localField: '_id',
foreignField: 'postId'
})
PostsSchema.virtual('commentCount', {
ref: 'PostComment',
localField: '_id',
foreignField: 'postId',
count: true
})
PostsSchema.set('toObject', { virtuals: true })
PostsSchema.set('toJSON', { virtuals: true })
const Posts = mongoose.model('Posts', PostsSchema)
Posts.consts = {
STATUS_INACTIVE: 0,
STATUS_ACTIVE: 1,
STATUS_DELETED: 2,
IS_FORWARDED: 1,
TYPE_TEXT: 1,
TYPE_IMAGE: 2,
TYPE_VIDEO: 3,
TYPE_ASK_TEXT: 4,
TYPE_ASK_IMAGE: 5,
TYPE_RATING: 6
}
module.exports = Posts
comments.model.js
const mongoose = require('mongoose')
var ObjectId = mongoose.Schema.Types.ObjectId
let PostCommentSchema = new mongoose.Schema({
userId: {
type: ObjectId,
ref: 'User'
},
postId: {
type: ObjectId,
ref: 'FanPosts'
},
comment: {
type: String
},
isReply: {
type: Number,
default: 0
},
parentCommentId: {
type: ObjectId,
ref: 'PostComment'
}
}, {
timestamps: true,
collection: 'post_comment'
})
PostCommentSchema.set('toObject', { virtuals: true })
PostCommentSchema.set('toJSON', { virtuals: true })
PostCommentSchema.virtual('replies', {
ref: 'PostComment',
localField: '_id',
foreignField: 'parentCommentId'
})
PostCommentSchema.virtual('repliesCount', {
ref: 'PostComment',
localField: '_id',
foreignField: 'parentCommentId',
count: true,
justOne: true
})
const PostComment = mongoose.model('PostComment', PostCommentSchema)
PostComment.consts = {
TYPE_NOT_REPLY: 0,
TYPE_REPLY: 1
}
module.exports = PostComment
Query:
Posts.find({celebrityId: celebrityId, status: Posts.consts.STATUS_ACTIVE})
.populate({ path: 'userId', select: 'fmId fullName' })
.populate({ path: 'isMyPost', match:{_id: userId} })
.populate({ path: 'comments', match: {isReply: PostComment.consts['TYPE_NOT_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'}, populate: {path: 'replies', match: {isReply: PostComment.consts['TYPE_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'} } })
.populate({ path: 'commentCount'})
.exec(function(err, posts){
if (err) return res.send({status: status.codes.http['serverError'], message: err})
return res.send({status: status.codes.http['success'], posts: posts})
})
Result:
{
"status": 200,
"posts": [
{
"type": 1,
"isForwarded": 0,
"originalPostId": null,
"status": 1,
"_id": "5d2b16519788076fafe7700c",
"celebrityId": "5d167ca099a55c2d2494dcf8",
"post": "hi how are you",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"createdAt": "2019-07-14T11:47:29.863Z",
"updatedAt": "2019-07-14T11:47:29.863Z",
"__v": 0,
"isMyPost": 1,
"comments": [
{
"isReply": 0,
"_id": "5d33721a12aba934e6520f2d",
"userId": "5d167a397213b127aafb48f3",
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"createdAt": "2019-07-20T19:57:14.747Z",
"updatedAt": "2019-07-20T19:57:14.747Z",
"__v": 0,
"replies": [
{
"isReply": 1,
"_id": "5d33724e12aba934e6520f2e",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"parentCommentId": "5d33721a12aba934e6520f2d",
"createdAt": "2019-07-20T19:58:06.286Z",
"updatedAt": "2019-07-20T19:58:06.286Z",
"__v": 0,
"id": "5d33724e12aba934e6520f2e"
}
],
"id": "5d33721a12aba934e6520f2d"
}
],
"commentCount": 2,
"id": "5d2b16519788076fafe7700c"
},
]
}
I need to populate userId inside comments object and add repliesCount to the replies object.
The issue is, I can populate only one column. In the query, if you see I would have given populate for both userId and replies. Since replies being the last I am getting the replies data.
I don't know how to populate both replies and userId
Expected:
{
"status": 200,
"posts": [
{
"type": 1,
"isForwarded": 0,
"originalPostId": null,
"status": 1,
"_id": "5d2b16519788076fafe7700c",
"celebrityId": "5d167ca099a55c2d2494dcf8",
"post": "hi how are you",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"createdAt": "2019-07-14T11:47:29.863Z",
"updatedAt": "2019-07-14T11:47:29.863Z",
"__v": 0,
"isMyPost": 1,
"comments": [
{
"isReply": 0,
"_id": "5d33721a12aba934e6520f2d",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"createdAt": "2019-07-20T19:57:14.747Z",
"updatedAt": "2019-07-20T19:57:14.747Z",
"__v": 0,
"replies": [
{
"isReply": 1,
"_id": "5d33724e12aba934e6520f2e",
"userId": {
"_id": "5d167a397213b127aafb48f3",
"fmId": "FM499KNWDL",
"fullName": "Mohideen Abubucker"
},
"postId": "5d2b16519788076fafe7700c",
"comment": "comment 1",
"parentCommentId": "5d33721a12aba934e6520f2d",
"createdAt": "2019-07-20T19:58:06.286Z",
"updatedAt": "2019-07-20T19:58:06.286Z",
"__v": 0,
"id": "5d33724e12aba934e6520f2e",
"repliesCount": 1
}
],
"id": "5d33721a12aba934e6520f2d"
}
],
"commentCount": 2,
"id": "5d2b16519788076fafe7700c"
},
]
}

The problem here:
.populate({ path: 'comments', match: {isReply: PostComment.consts['TYPE_NOT_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'}, populate: {path: 'replies', match: {isReply: PostComment.consts['TYPE_REPLY']}, populate: {path: 'userId', select: 'fmId fullName'} } })
Your populate parameters:
{
path: 'comments',
match: {
isReply: PostComment.consts['TYPE_NOT_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
},
populate: {
path: 'replies',
match: {
isReply: PostComment.consts['TYPE_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
}
}
}
Object literals are a flavor of key value map thus you cannot have multiple keys of the same value within a single level of the object.
You have 2 keys with the value "populate" at a single level of the object and it looks like only the last one is persisted in the object.
You can see that here: https://jsfiddle.net/32oe6w8y/1/
Check the mongoose documentation, I'm sure they have a mechanism to deal with this (populate may take an array.)
EDT:
Based on this you can pass an array into populate:
https://stackoverflow.com/a/21100156/2004999
That may be the solution to your problem.
Fixed problematic populate parameters:
{
path: 'comments',
match: {
isReply: PostComment.consts['TYPE_NOT_REPLY']
},
populate: [{
path: 'userId',
select: 'fmId fullName'
},{
path: 'replies',
match: {
isReply: PostComment.consts['TYPE_REPLY']
},
populate: {
path: 'userId',
select: 'fmId fullName'
}
}]
}

Related

populate collection is returning an empty array in mongoose using express

This is my how my Like model schema looks like.
//create likes schema
const likes = mongoose.Schema({
liked: {
type: Boolean,
default: false
},
tweet: {
type: Schema.Types.ObjectId,
ref: "Tweet"
},
author: {
type: Schema.Types.ObjectId,
ref: "User"
}
});
module.exports = mongoose.model('Like', likes);
and this is my an overview of my Tweet Schema:
const tweets = mongoose.Schema({
content: {
type: String,
required: true,
},
author: {
type: Schema.Types.ObjectId,
ref: "User"
},
likes: [{
type: Schema.Types.ObjectId,
ref: "Like"
}]
});
module.exports = mongoose.model('Tweet', tweets);
I am testing on based on the following data from
const likes = await Like.find().populate("author", "_id name email").populate("tweet", "_id content").exec()
res.json(likes)
[
{
"_id": "63921e53deb31c60249901e4",
"liked": true,
"tweet": {
"_id": "63921e50deb31c60249901e1",
"content": "tweet 1"
},
"author": {
"_id": "63921e2ddeb31c60249901dd",
"name": "Dave",
"email": "something#gmail.com"
},
"createdAt": "2022-12-08T17:26:43.650Z",
"updatedAt": "2022-12-08T17:26:43.650Z",
"__v": 0
}
]
And this is how I am using the populate method to fetch the likes of a tweet.
const tweets = await Tweet.find()
.populate("author", "_id name email")
.populate("likes", "_id")
.sort({updatedAt: "desc"})
.exec()
res.status(200).json(tweets)
but I am getting an empty array in likes collection (of objects).
[
{
"_id": "6393701aa62997f3454e81e1",
"content": "My tweet",
"author": "63936ffaa62997f3454e81dd",
"likes": [],
"createdAt": "2022-12-09T17:27:54.146Z",
"updatedAt": "2022-12-09T17:27:54.146Z",
"__v": 0
}
]
Followed this documentation
this is the data from likes schema
[
{
"_id": "63937140df6222756bd84ede",
"liked": true,
"tweet": {
"_id": "6393701aa62997f3454e81e1",
"content": "My tweet"
},
"author": {
"_id": "63936ffaa62997f3454e81dd",
"name": "Dave",
"email": "admin#gmail.com"
},
"createdAt": "2022-12-09T17:32:48.251Z",
"updatedAt": "2022-12-09T17:32:48.251Z",
"__v": 0
}
]
As i understand, you want to populate nested objects
Mongoose populate syntax is:
populate({ path: 'refKey'}).
I would suggest you use aggregate.
const tweets = await Tweet.aggregate([
{
$lookup: {
from: "user",//your schema name in mongoose db
localField: "author",//field name from Tweet which contains the id of user(auther)
foreignField: "_id",//_id of user(auther) model
pipeline: [
{
$project: {
"_id": 1,
"name": 1,
"email": 1,
}
}
],
as: "author"
}
},
{
$lookup: {
from: "likes",//your schema name in mongoose db
localField: "likes",// or likes._id
foreignField: "_id",
pipeline: [
{
$project: {
"_id": 1,
}
}
],
as: "likes"
}
},
{
$sort: {
updatedAt: -1
}
}
])

mongoose move ref document to main document as DTO

I wanna to move sub document to main documents, and return single DTO without any nested document, below is my sample data.js
data.js
const mongoose = require('mongoose');
//city
const citySchema = new mongoose.Schema({
cityName: { type: String, required: true, unique: true },
});
const City = mongoose.model('City', citySchema);
//country
const countrySchema = new mongoose.Schema({
countryName: { type: String, required: true, unique: true },
});
const Country = mongoose.model('Country', countrySchema);
//user
const userSchema = new mongoose.Schema({
username: { type: String, required: true },
city: {
type: mongoose.Schema.Types.ObjectId,
ref: 'City',
required: true,
},
country: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Country',
required: true,
},
createdAt: { type: Date, required: true },
updatedAt: { type: Date, required: true },
});
const User = mongoose.model('User', userSchema);
function getUser(id) {
return User.findById(id)
.populate('city')
.populate('country')
.exec();
};
Current return JSON Response for User:
{
"_id": "6321ac3d14a57c2716f7f4a0",
"name": "David",
"city": {
"_id": "63218ce557336b03540c9ce9",
"cityName": "New York",
"__v": 0
},
"country": {
"_id": "632185bbe499d5505cafdcbc",
"countryName": "USA",
"__v": 0
},
"createdAt": "2022-09-14T10:26:05.000Z",
"__v": 0
}
How do I move the cityName and countryName to main model, and response JSON as below format?
{
"_id": "6321ac3d14a57c2716f7f4a0",
"username": "David",
"cityName": "New York",
"countryName": "USA",
"createdAt": "2022-09-14T10:26:05.000Z",
}
Using aggregation you can try something like this:
db.user.aggregate([
{
"$match": {
_id: "6321ac3d14a57c2716f7f4a0"
}
},
{
"$lookup": {
"from": "city",
"localField": "city",
"foreignField": "_id",
"as": "city"
}
},
{
"$lookup": {
"from": "country",
"localField": "country",
"foreignField": "_id",
"as": "country"
}
},
{
"$addFields": {
"country": {
"$arrayElemAt": [
"$country",
0
]
},
"city": {
"$arrayElemAt": [
"$city",
0
]
}
}
},
{
"$addFields": {
"countryName": "$country.countryName",
"cityName": "$city.cityName"
}
},
{
"$unset": [
"country",
"city"
]
}
])
Here's the playground link.
The other probably simpler way of doing this would be, modify your function like this:
function getUser(id) {
const user = User.findById(id)
.populate('city')
.populate('country')
.exec();
if(user.country) {
user.countryName = user.country.countryName;
}
if(user.city) {
user.cityName = user.city.cityName;
}
delete user.country;
delete user.city;
return user
};

How to find all voted user against each debate in mongoose

I have a debate collection, which holds all the debates and another collection holds the votes against each debate. So I wanted to retrieve all the debates with a new user flag (isVoted) if I found any user in the vote collection against each debate.
Vote model:
var voteSchema = new Schema({
user: { type: Schema.Types.ObjectId, required: true, ref: 'User' }, // One who votes
debate: { type: Schema.Types.ObjectId, required: true, ref: 'Debate' }
}, { timestamps: true });
Debate Model:
var debateSchema = new Schema({
category: { type: Schema.Types.ObjectId, required: true, ref: 'Category' },
question: { type: String, required: true },
Votes: { type: Number, default: 0 },
}, { timestamps: true });
Query
DebateData.aggregate([
{
$match: query
},
{
$sort : { createdAt : -1 }
},
{
$lookup: {
from: "votes", // must be the PHYSICAL name of the collection
localField: "_id",
foreignField: "debate",
as: "votes"
}
},
{
$addFields: {
'isVoted': {
$cond: { if: { $eq: [ '$votes.user', ObjectId(req.query.userId) ] }, then: 'true', else: 'false' }
}
}
},
{
$project: {
'_id': 1,
'question': 1,
'isVoted': 1,
'createdAt': 1
}
},
]).then(result => {
res.status(200).json({ success: true, status: 200, message: 'Debate videos', data: result});
}).catch(err => {
res.status(500).json({ success: false, status: 500, message: err.message })
});
Expected output:
{
"data": [
{
"_id": "60e81f8299a4809658290d80",
"votes": 10,
"category": [
{
"name": "Hockey"
}
],
"question": "What is football?",
"isVoted": true,
"createdAt": "2021-07-09T10:05:54.498Z"
},
{
"_id": "60e438f1194949add0cc2074",
"votes": 12,
"category": [
{
"name": "Cricket"
}
],
"question": "What is football?",
"isVoted": false,
"createdAt": "2021-07-06T11:05:21.654Z"
}
]
}
Current output:
{
"data": [
{
"_id": "60e81f8299a4809658290d80",
"votes": 10,
"category": [
{
"name": "Hockey"
}
],
"question": "What is football?",
"createdAt": "2021-07-09T10:05:54.498Z"
},
{
"_id": "60e438f1194949add0cc2074",
"votes": 12,
"category": [
{
"name": "Cricket"
}
],
"question": "What is football?",
"createdAt": "2021-07-06T11:05:21.654Z"
}
]
}
Vote Data:
{
data: [
{
"user": "69881f8299a480965829ytr267",
"debate": "60e81f8299a4809658290d80"
}
]
}

I need to aggregate two collections based on userIds but couldn't manage it

I want to aggregate the collections (Review and Account) below but couldn't manage it properly so I needed to ask you guys.
Current Review Collection is written below
{
lawyerId: { type: mongoose.Schema.Types.ObjectId },
reviews: [
{
userId: { type: mongoose.Schema.Types.ObjectId, unique: true },
message: { type: String },
rate: { type: Number },
createdAt: { type: Date, default: Date.now },
},
],
}
If you recommend Review Collection can be refactored like this
{
lawyerId: { type: mongoose.Schema.Types.ObjectId },
userId: { type: mongoose.Schema.Types.ObjectId },
message: { type: String },
rate: { type: Number },
createdAt: { type: Date, default: Date.now },
}
Account Collection
{
_id: { type: mongoose.Schema.Types.ObjectId}
email: { type: String, unique: true },
firstName: { type: String },
lastName: { type: String },
},
The expected result of fetching reviews
{
averageRate: 3.2,
reviews: [
{
firstName: 'Jack',
lastName: 'Harden',
message: 'I dont like it',
rate: 2,
createdAt: '2020-01-01T14:58:23.330+00:00'
},
{
firstName: 'Takeshi',
lastName: 'San',
message: 'Thats nice',
rate: 5,
createdAt: '2020-03-02T10:45:10.120+00:00'
}
],
}
You should be able to achieve this using an aggregation.
You can view a live demo here, which allows you to run this query.
The Query:
// Assuming we are searching for an lawyerId of 3
db.review.aggregate([
{
$match: {
lawyerId: 3
}
},
{
$lookup: {
from: "account",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$group: {
_id: "$lawyerId",
averageRate: {
$avg: "$rate"
},
reviews: {
$push: {
createdAt: "$createdAt",
firstName: "$user.firstName",
lastName: "$user.lastName",
message: "$message",
rate: "$rate"
}
}
}
},
{ // *******************************************
$project: { // *******************************************
_id: 0, // If you comment out/remove all of these lines
averageRate: 1, // then the return also contains the 'lawyerId',
reviews: 1 // as '_id', which I would find useful...
} // *******************************************
} // *******************************************
])
The Results:
The query from above, using the data set from above, produces the following results:
[
{
"averageRate": 3.25,
"reviews": [
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "First",
"lastName": "Uno",
"message": "Message meh",
"rate": 3
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "Second",
"lastName": "Dos",
"message": "Message blah",
"rate": 4
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "First",
"lastName": "Uno",
"message": "Message foo",
"rate": 4
},
{
"createdAt": ISODate("2015-02-28T00:00:00Z"),
"firstName": "Third",
"lastName": "Tres",
"message": "Message bar",
"rate": 2
}
]
}
]
The Dataset:
In Mongo Playground, you can build out databases with multiple collections, this explains the data structure:
db={ // <---- Database 'db'
"account": [ // <---- Collection 'account'
{
_id: 21,
email: "first.uno#gmail.com",
firstName: "First",
lastName: "Uno"
},
{
_id: 22,
email: "second.dos#yahoo.com",
firstName: "Second",
lastName: "Dos"
},
{
_id: 23,
email: "third.tres#hotmail.com",
firstName: "Third",
lastName: "Tres"
}
],
"review": [ // <---- Collection 'review'
{
lawyerId: 3,
userId: 21,
message: "Message meh",
rate: 3,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 22,
message: "Message blah",
rate: 4,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 21,
message: "Message foo",
rate: 4,
createdAt: ISODate("2015-02-28T00:00:00Z")
},
{
lawyerId: 3,
userId: 23,
message: "Message bar",
rate: 2,
createdAt: ISODate("2015-02-28T00:00:00Z")
}
]
}
You can try this pipeline to get all reviews from review collection:
db.reviews.aggregate([
{
$lookup: {
from: "accounts",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{
$unwind: "$user"
},
{
$addFields: {
"firstName": "$user.firstName",
"lastName": "$user.lastName"
}
},
{
$group: {
"_id": null,
"average_rate": {
$avg: "$rate"
},
"reviews": {
$push: "$$ROOT"
}
}
},
{
$unset: [
"_id",
"reviews._id",
"reviews.user",
"reviews.userId",
"reviews.lawyerId"
]
}
])
Results:
[
{
"average_rate": 3.5,
"reviews": [
{
"createdAt": "Some Review Date",
"firstName": "Jack",
"lastName": "Harden",
"message": "I dont like it",
"rate": 2
},
{
"createdAt": "Some Review Date",
"firstName": "Takeshi",
"lastName": "San",
"message": "That's nice",
"rate": 5
}
]
}
]
Demo here

fetch 3 schema value using mongoose node JS

I have 3 collection schema CategorySchema, SubCategorySchema, ProductSchema like below.
var CategorySchema = new mongoose.Schema({
catgory_name: {
type: String,
required: [true, "Catgory name is required"]
},
modified_date: {
type: Date
}
});
module.exports = mongoose.model("Category", CategorySchema);
var SubCategorySchema = new Schema({
subcatgory_name: {
type: String,
required: [true, "subcategory name is required"]
},
category_id: {
type: Schema.Types.ObjectId,
ref: "Category",
required: [true, "category id is required"]
},
modified_date: {
type: Date
},
is_active: {
type: Boolean,
default: 1
}
});
module.exports = mongoose.model("SubCategories", SubCategorySchema);
const ProductSchema = new Schema({
product_name: {
type: String,
required: [true, "Product name is required"]
},
product_image: {
type: String,
required: [true, "Product image is required"]
},
category_id: {
type: Schema.Types.ObjectId,
ref: "Category",
required: [true, "category is required"]
},
subcategory_id: {
type: Schema.Types.ObjectId,
ref: "Subcategory",
required: [true, "Subcategory is required"]
},
modified_date: {
type: Date
},
is_active: {
type: Boolean,
default: 1
}
});
module.exports = mongoose.model("Products", ProductSchema);
Here i want to take all the active products (is_active = 1) with the corresponding categories and active subcategories (is_active = 1). No need to check is_active condition for categories but need to check active condition for subcategories and products
I tried with the below code in node JS controller
router.get("/list", (req, res, next) => {
products
.find({ is_active: true })
.populate("category_id")
.populate("subcategory_id", null, SubCategory, {
match: { is_active: true }
})
//.where("subcategory_id", !null)
.then(products => res.json({ status: 200, data: products }))
.catch(err => res.json(err));
});
But even subcategories are inactive it returns the product data
You can query using mongodb aggregation framework still using mongoose.
router.get("/list", (req, res, next) => {
products
.aggregate([
{
$match: {
is_active: true
}
},
{
$lookup: {
from: "subcategories",
localField: "subcategory_id",
foreignField: "_id",
as: "subcategories"
}
},
{
$unwind: "$subcategories"
},
{
$match: {
"subcategories.is_active": true
}
},
{
$lookup: {
from: "categories",
localField: "category_id",
foreignField: "_id",
as: "category"
}
},
{
$addFields: {
category: {
$arrayElemAt: ["$category", 0]
}
}
}
])
.then(products => res.json({ status: 200, data: products }))
.catch(err => res.status(500).json(err));
});
Playground
Let's have these sample documents:
db={
"products": [
{
"modified_date": "2020-01-08T09:06:51.544Z",
"is_active": true,
"_id": "5e159ca1bd95457404b22bc3",
"product_name": "Product1 Name",
"product_image": "Product1 Image",
"category_id": "5e159b77a746036404b5f0ae",
"subcategory_id": "5e159befbd95457404b22bc2"
},
{
"modified_date": "2020-01-08T09:06:51.544Z",
"is_active": false,
"_id": "5e159cb8bd95457404b22bc4",
"product_name": "Product2 Name",
"product_image": "Product2 Image",
"category_id": "5e159b77a746036404b5f0ae",
"subcategory_id": "5e159befbd95457404b22bc2"
},
{
"modified_date": "2020-01-08T09:06:51.544Z",
"is_active": true,
"_id": "5e159d3abd95457404b22bc6",
"product_name": "Product3 Name",
"product_image": "Product3 Image",
"category_id": "5e159b77a746036404b5f0ae",
"subcategory_id": "5e159ce0bd95457404b22bc5"
}
],
"categories": [
{
"modified_date": "2020-01-08T09:04:18.003Z",
"_id": "5e159b77a746036404b5f0ae",
"catgory_name": "Main Category 1"
}
],
"subcategories": [
{
"modified_date": "2020-01-08T09:06:51.544Z",
"is_active": true,
"_id": "5e159befbd95457404b22bc2",
"subcatgory_name": "Sub Category 1",
"category_id": "5e159b77a746036404b5f0ae"
},
{
"modified_date": "2020-01-08T09:06:51.544Z",
"is_active": false,
"_id": "5e159ce0bd95457404b22bc5",
"subcatgory_name": "Sub Category 2",
"category_id": "5e159b77a746036404b5f0ae"
}
]
}
The result will be:
[
{
"_id": "5e159ca1bd95457404b22bc3",
"category": {
"_id": "5e159b77a746036404b5f0ae",
"catgory_name": "Main Category 1",
"modified_date": "2020-01-08T09:04:18.003Z"
},
"category_id": "5e159b77a746036404b5f0ae",
"is_active": true,
"modified_date": "2020-01-08T09:06:51.544Z",
"product_image": "Product1 Image",
"product_name": "Product1 Name",
"subcategories": {
"_id": "5e159befbd95457404b22bc2",
"category_id": "5e159b77a746036404b5f0ae",
"is_active": true,
"modified_date": "2020-01-08T09:06:51.544Z",
"subcatgory_name": "Sub Category 1"
},
"subcategory_id": "5e159befbd95457404b22bc2"
}
]
As you see, even the Product 3 is active, it hasn't been retrieved because its subcategory 5e159ce0bd95457404b22bc5 is not active.

Resources