simplifying deep nested mongo query without using aggregate

Multi tool use


simplifying deep nested mongo query without using aggregate
I'm trying to create a sort of newsfeed feature for my app. I'm trying to understand how to do it with a nested query in Mongo/Mongoose rather than using aggregate. (the rest of the app uses nested queries) and I'd prefer to not have to modify the query with vanilla javascript to get the perfect object if I don't need to.
I'd like to get the friends of the current user, for each friend, get all of their posts and then sort it all by date.
I want to make my current query more efficient as there's an extra step
my query in Mongoose
User.findOne({ _id: req.userId }, 'friends.user -_id')
.populate({
path: 'friends.user',
model: 'User',
select: 'posts -_id',
populate: {
path: 'posts',
model: 'Post',
select: 'date author user desc -_id',
options: { sort: { date: -1 } },
populate: {
path: 'author user',
model: 'User',
select: 'profile.firstname profile.lastname profile.avatar username'
},
},
})
the results
{
"newsfeed": [
{
"user": {
"posts": [
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "kenne"
},
"author": {
"profile": {
"firstname": "user1",
"lastname": "user1",
"avatar": "9e7b60e534cf761f41d6afe3d97295c9.jpg"
},
"_id": "5b562382a16cde19638e4bee",
"username": "user1"
},
"date": "2018-07-24T06:40:37.413Z",
"desc": "sdfsdf"
},
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"author": {
"profile": {
"firstname": "user1",
"lastname": "user1",
"avatar": "9e7b60e534cf761f41d6afe3d97295c9.jpg"
},
"_id": "5b562382a16cde19638e4bee",
"username": "user1"
},
"date": "2018-07-24T06:40:17.180Z"
},
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"author": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"date": "2018-07-23T22:20:15.246Z",
"desc": "Gibberish"
}
]
}
},
{
"user": {
"posts": [
{
"user": {
"profile": {
"firstname": "user3",
"lastname": "user3",
"avatar": "132df94df5733efd41609681bc7f71f9.jpeg"
},
"_id": "5b5382f3661a8d7023e1ae65",
"username": "user3"
},
"author": {
"profile": {
"firstname": "user3",
"lastname": "user3",
"avatar": "132df94df5733efd41609681bc7f71f9.jpeg"
},
"_id": "5b5382f3661a8d7023e1ae65",
"username": "user3"
},
"date": "2018-07-21T19:09:45.543Z",
"desc": "Gibberish"
}
]
}
}
]
}
because I need to first get the friends of the user, the current query creates two user objects each with their own posts and authors and user objects within them.
eg. {"newsfeed": [{"user": {"bios": [...]}}], [{"user": {"bios": [...]}}] }
what I want is something like this
{
"newsfeed": [{
"posts": [
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"author": {
"profile": {
"firstname": "user1",
"lastname": "user1",
"avatar": "9e7b60e534cf761f41d6afe3d97295c9.jpg"
},
"_id": "5b562382a16cde19638e4bee",
"username": "user1"
},
"date": "2018-07-24T06:40:37.413Z",
"desc": "sdfsdf"
},
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"author": {
"profile": {
"firstname": "user1",
"lastname": "user1",
"avatar": "9e7b60e534cf761f41d6afe3d97295c9.jpg"
},
"_id": "5b562382a16cde19638e4bee",
"username": "user1"
},
"date": "2018-07-24T06:40:17.180Z"
},
{
"user": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"author": {
"profile": {
"firstname": "user2",
"lastname": "user2",
"avatar": "c66620a0ef057908a1663725956ac03a.jpg"
},
"_id": "5b56549fcba9231e5d1e848d",
"username": "user2"
},
"date": "2018-07-23T22:20:15.246Z",
"desc": "Gibberish"
},
{
"user": {
"profile": {
"firstname": "user3",
"lastname": "user3",
"avatar": "132df94df5733efd41609681bc7f71f9.jpeg"
},
"_id": "5b5382f3661a8d7023e1ae65",
"username": "user3"
},
"author": {
"profile": {
"firstname": "user3",
"lastname": "user3",
"avatar": "132df94df5733efd41609681bc7f71f9.jpeg"
},
"_id": "5b5382f3661a8d7023e1ae65",
"username": "user3"
},
"date": "2018-07-21T19:09:45.543Z",
"desc": "Gibberish"
}
]}
How can I do that without using aggregate? to get something more like this
eg. {"newsfeed": [{"posts": [...]}] }
additional info
users
const UserSchema = new Schema({
username : String,
friends : [{ user: { type: Schema.Types.ObjectId, ref: 'User'}, status: String }],
profile: {
name : String,
firstname : String,
lastname : String,
avatar : String
}),
posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
posts
const PostsSchema = new Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
author : { type: Schema.Types.ObjectId, ref: 'User' },
date : Date,
desc : String
});
example data: users
{
"_id" : ObjectId("5b51fd2a3f4ec33546a06648"),
"profile" : {
"firstname" : "user1",
"lastname" : "user1"
"avatar" : "user1.png"
}
"username" : "user1",
"friends" : [
{
"_id" : ObjectId("5b51fd7c3f4ec33546a0664f"),
"user" : ObjectId("5b51fd643f4ec33546a0664c")
},
{
"_id" : ObjectId("5b51fdb53f4ec33546a06655"),
"user" : ObjectId("5b51fd903f4ec33546a06652")
}
]
}
{
"_id" : ObjectId("5b51fd643f4ec33546a0664c"),
"profile" : {
"firstname" : "user2",
"lastname" : "user2"
"avatar" : "user2.png"
}
"posts" : [
{
"_id" : ObjectId("5b51fdcd3f4ec33546a06610"),
"_id" : ObjectId("5b51fdcd3f4ec33546a06611"),
"_id" : ObjectId("5b51fdcd3f4ec33546a06612"),
}
],
"__v" : 5,
"username" : "user2"
},
{
"_id" : ObjectId("5b51fd903f4ec33546a06652"),
"profile" : {
"firstname" : "user3",
"lastname" : "user3"
"avatar" : "user3.png"
},
"posts" : [
{
"_id" : ObjectId("5b51fdce3f4ec33546a06615"),
"_id" : ObjectId("5b51fd2a3f4ec33546a06617")
}
],
"__v" : 5,
"username" : "user3"
}
example date: posts
{
"_id" : ObjectId("5b51fdcd3f4ec33546a06610"),
"user" : ObjectId("5b51fd2a3f4ec33546a06648"),
"author" : ObjectId("5b51fd2a3f4ec33546a06648"),
"date" : ISODate("2018-07-20T15:18:02.962Z"),
"desc" : "user1 gibberish",
"__v" : 0
}
{
"_id" : ObjectId("5b51fdcd3f4ec33546a06611"),
"user" : ObjectId("5b51fd643f4ec33546a0664c"),
"author" : ObjectId("5b51fd643f4ec33546a0664c"),
"date" : ISODate("2018-07-20T15:19:00.968Z"),
"desc" : "user2 gibberish",
"__v" : 0
}
{
"_id" : ObjectId("5b51fdcd3f4ec33546a06612"),
"user" : ObjectId("5b51fd903f4ec33546a06652"),
"author" : ObjectId("5b51fd903f4ec33546a06652"),
"date" : ISODate("2018-07-20T15:19:44.102Z"),
"desc" : "user3 gibberish",
"__v" : 0
}
{
"_id" : ObjectId("5b51fdce3f4ec33546a06615"),
"user" : ObjectId("5b51fd643f4ec33546a0664c"),
"author" : ObjectId("5b51fd643f4ec33546a0664c"),
"date" : ISODate("2018-07-20T15:19:00.968Z"),
"desc" : "more gibberish",
"__v" : 0
}
{
"_id" : ObjectId("5b51fdce3f4ec33546a06616"),
"user" : ObjectId("5b51fd903f4ec33546a06652"),
"author" : ObjectId("5b51fd903f4ec33546a06652"),
"date" : ISODate("2018-07-20T15:19:44.102Z"),
"desc" : "more gibberish",
"__v" : 0
}
2 Answers
2
User.findOne({ _id: req.userId }, { "friends.user": 1 })
.exec(function(err, { friends }) { // get the list of friends
const friendIds = friends.map(({ user }) => user);
Post.find({ user: { $in: friendIds } }) // find all posts of those friends
.populate({
path: 'user author',
model: 'User',
select: 'profile.firstname profile.lastname profile.avatar username -_id'
})
.sort('-date')
.exec(function(err, response) {
.....
})
})
You could try unrolling the items in the application layer with the following code
User.find({ _id: mongoose.Types.ObjectId("5b51fd2a3f4ec33546a06648") }, 'friends.user -_id')
.populate({
path: 'friends.user',
model: 'User',
select: 'posts -_id',
})
.exec(function(err, results) {
// Post.find({ _id: {$in: results
const postIDs = results.reduce((acc, user) => {
return acc.concat(
...user.friends.map(
friend => friend.user.posts
)
)
}, )
Post.find({ _id: { $in: postIDs } })
.populate(
{
path: 'author user',
model: 'User',
select: 'profile.firstname profile.lastname profile.avatar username'
}
)
.exec(function(err, posts) {
console.log(
JSON.stringify(posts, null, 2)
);
})
});
Other option is map reduce, but since you don't like aggregation, I'm not sure map reduce is appropriate either.
const mapReduceConfig = {
map: function() {
var friends = this.friends;
emit(
this._id,
friends.reduce(
(acc, friend) => {
return acc.concat(
friend.posts &&
friend.posts.reduce((pacc, p) => pacc.concat(p), )
) || ;
},
)
);
},
reduce: function(k, vals) {
return Array.sum(vals);
}
};
User.mapReduce(mapReduceConfig, function(err, results) {
console.log(
JSON.stringify(results, null, 2)
);
});
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.