mongodb aggregate $lookup vs find and populate - node.js

I have a Video Schema like this:
const VideoSchema = new mongoose.Schema({
caption: {
type: String,
trim: true,
maxlength: 512,
required: true,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
// some more fields
comments: [{
type: mongoose.Schema.ObjectId,
ref: 'Comment',
}],
commentsCount: {
type: Number,
required: true,
default: 0,
},
}, { timestamps: true });
and a simple Comment schema like this:
const CommentSchema = new mongoose.Schema({
text: {
type: String,
required: true,
maxLength: 512,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
videoId: {
type: mongoose.Schema.ObjectId,
ref: 'Video',
required: true,
index: true,
},
}, { timestamps: true });
and with schemas like this I'm able to perform any kind of find query on my Video collection and populate it with its comments:
Video.find({ owner: someUserId }).populate({ path: 'comments' });
My question is how necessary is it to keep comment ids inside video collection? given that I have indexed the videoId field in my Comment schema, how bad it would be (speaking of performance) to get rid of these comment ids and the count of them and use aggregation $lookup to find a video's comments like this:
Video.aggregate([
{
$match: {
owner: someUserId,
},
},
{
$lookup: {
from: 'comments',
localField: '_id',
foreignField: 'videoId',
as: 'comments',
}
}
])
How different are these in terms of performance?

Well there is no way the $lookup would be faster than having the list of the comment ids on the actual video object. I mean you have to do a whole other request to mongo to get them now. So performance wise obviously the lookup would add time. That is assuming you are not using mongoose populate to "convert" those comment ids into the referenced objects.
If you are then removing the comments from the video (as well as the actual count prop) and doing the lookup is the way to go. Since you are matching right away in your arg and then doing a simple lookup I do not see how this would be a bottleneck for you. Also you can optimize/change/tune your aggregation vie explain etc.
You video schema would be pretty clean that way:
const VideoSchema = new mongoose.Schema({
caption: {
type: String,
trim: true,
maxlength: 512,
required: true,
},
owner: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true,
},
// some more fields
}, { timestamps: true });

Related

how reuse a like model in nodejs

I have 2 models named "posts" and "status" and want to implement likes in them. first of all, I want the like to be able to record data like timestamps and other stuff depending on how it grows, which is why I made "like" be a model of its own.
The issue is since "posts" and "status" two models of their own are going to have "like" functionality.
Is there a way I could reuse the "like" model, instead of creating a separate "like" model for "posts" and "status", or how would you personally implement something like this?
Below is the post model
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
}
}, {
timestamps: true
})
below is the status model
const statusSchema = new mongoose.Schema({
Body: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
tags: [{
type: mongoose.Schema.Types.ObjectId,
required: True,
ref: 'User'
}]
}, {
timestamps: true
})
here is the like model which I would like users to be able to like both posts by users and statuses, while still able to retain information like the time it was liked and other information depending on the growth and need
const likeSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
likedObject: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Posts'
}
}, {
timestamps: true
})
Is there a way I could reuse the "like" model, instead of creating a separate "like" model for "posts" and "status" to capture the users and the time that they liked other user's statuses and posts?
Making Like Model was over complicating the entire thing so you could have something like this in your postSchema.
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
timeStamps:true,
//Adding the like property in each post but setting it to a default of 0
likes:{type:Number, default:0}
})
Now the key is identifying the post when like button is smashed
So what you could do on the client side is making sure that when like button is clicked you have an ID of the post to the function that your calling and, then pass that ID back to the server and you can have a logic like this below on your server and you would be able to add like functionality..
Server logic for adding like functionality
router.post("/api/likes/:id", async (request, response) => {
const post_id = request.params.id;
const post = await postModel.findOne({ _id: post_id });
post.likes += 1;
const updateDocument = await postModel.findOneAndUpdate(
{ _id: post_id },
post,
{
new: true,
}
);
return response.status(201).json({ msg: "Liked post" });
});
So the idea is two always be updating that specific document

Best way to structure mongoDB schemas for a Trello-like app using mongoose?

Right now i'm thinking of doing it like this:
Board:
const BoardSchema = new Schema({
title: {
type: String,
required: true,
},
admins: [
{
type: ObjectId,
ref: 'User',
required: true,
}
],
members: [
{
type: ObjectId,
ref: 'User',
required: true,
}
],
}, {
timestamps: true,
});
List:
const listSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
order: {
type: Number,
required: true,
},
boardId: {
type: ObjectId,
required: true,
ref: 'Board',
},
});
Card:
const cardSchema = new Schema({
text: {
type: String,
required: true,
},
members: {
type: ObjectId,
ref: 'User',
required: true,
},
listId: {
type: ObjectId,
ref: 'User',
required: true,
},
boardId: {
type: ObjectId,
ref: 'Board',
required: true,
},
order: {
type: Number,
required: true,
},
});
I'm new to mongoDB/noSQL and a database noob in general, and structuring a database seems to not be a very strict science, as everyone seems to do it differently. From what i've seen the rule of thumb is to keep as much data in one collection as possible, basically the opposite of traditional databases, but not always? So should i instead save everything in the Board collection?
Going forward with this project i want to try to replicate how one would do in a real scenario to learn as much as possible. And i've been googling around to try and find the schema structure for the real trello app but i cannot find it. All i know is that they do use mongoDB and they have a separate Collection for Cards because with the amount of data they handle it wouldn't work otherwise. So i assume they have a separate Collection for Lists (and Boards) aswell.
So my question is if these Schemas would work? And what would be the best way to go about getting all the data (when a user clicks on a board) using the boardId and combining it into a List of Cards?

Mongoose virtual with multiple foreignFields

I have a Schema Events which has a key creator which is a reference to the creator and a key musicians which is an array with the musicians.
A Musician can create Events or participate as a musician. I want to create a virtual property named events to the musician which will contain all the events that the musician has created and the events that participates.
I tried this:
MusicianSchema.virtual('events', {
ref: 'Events',
localField: '_id',
foreignField: ['creator, musicians'],
justOne: false,
});
but this returns an empty array. Is there any way to make it work, or mongoose doesn't have this feature (yet)?
EDIT
This is the Event Schema:
const eventSchema = {
title: {
type: String,
trim: true,
required: true
},
description: {
type: String,
trim: true
},
startDate: {
type: Number,
required: true
},
endDate: {
type: Number,
required: true
},
picture: {
type: String,
get: getPicture,
},
location: {
type: [Number],
index: '2dsphere',
get: getLocation
},
creator: {
type: mongoose.Schema.Types.ObjectId,
required: true,
refPath: 'creatorType'
},
musicians: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Musician'
}],
creatorType: {
type: String,
required: true,
enum: ['Musician', 'Entity']
}
};
And the Musicians Schema:
const musiciansSchema = {
name: {
type: String,
trim: true,
required: true
},
picture: {
type: String,
get: getPicture,
},
description: String,
type: {
type: String,
required: true,
enum: ['Band', 'Artist'],
},
};
Mongoose virtual with multiple foreignField feature is not available at the moment, Suggestion for new feature has already requested on GitHub,
For now you need to use separate virtual/populate
MusicianSchema.virtual('creatorEvents', {
ref: 'Events',
localField: '_id',
foreignField: 'creator',
justOne: false,
});
MusicianSchema.virtual('musiciansEvents', {
ref: 'Events',
localField: '_id',
foreignField: 'musicians',
justOne: false,
});
I was facing similar situation during development and what I did was to replicate the same code twice but with different virtual names and it works perfectly.
In your case:
​MusicianSchema​.​virtual​(​'createdEvents'​,​ ​{​
    ​ref​: ​'Events'​,​
    ​localField​: ​'_id'​,​
    ​foreignField​: ​'creator'​,​
    ​justOne​: ​false​,​
​}​)​;​
​MusicianSchema​.​virtual​(​'musicianEvents'​,​ ​{​
    ​ref​: ​'Events'​,​
    ​localField​: ​'_id'​,​
    ​foreignField​: ​'musician​,​
    ​justOne​: ​false​,​
​}​)​;​

How to keep track of videos watched with Node.js and Mongodb

I'm building a MEAN stack video app (I'm pretty new to Node and Mongodb) and I need a way to keep track of videos watched. How do I do this?
I was thinking I could have an array of Ids in the user collection that references videos but I'd like to be able to return videos with a watched: true key value pair that's dependent on the user making the request. If this is a good way to do it, how do I return a key value pair that's dependent on another document in another collection?
User model:
let UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
minlength: 1,
trim: true,
unique: true,
validate: {
validator: VALUE => validator.isEmail(VALUE),
message: '{VALUE} is not a valid email'
}
},
password: {
type: String,
required: true,
minlength: 6
},
admin: {
type: Boolean,
default: false
},
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }],
tokens: [{
access: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
Video Model:
var Video = mongoose.model('Video', {
url: {
type: String,
required: true,
minlength: 1,
trim: true
},
title: {
type: String,
required: true,
default: '',
trim: true
},
description: {
type: String,
default: '',
trim: true
},
img: {
type: String,
default: '',
trim: true
},
attachments: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Attachment' }]
});
vid_completed on the User model is where I'd like to keep track of the video ids that have been watched. And the Video model is what would be returned with a key: value pair based on whether the video id is found in the user vid_completed array. Let me know if that makes sense. Thanks in advance!
I ended up just using an array in the User model like so:
vid_inprogress: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Video'
},
vid_completed: [{ type : mongoose.Schema.Types.ObjectId, ref: 'Video' }]
I'll just have to add watched: true on the front end. Let me know if you have a better way. I'd be interested to hear.

Mongoose User Schema design and array of posts In ref

I Have schema design as below. I have posts array which is reference to the post model. Is it good idea to put it in User schema or should I not include as it is always growing as users add their post. I guess I should only put accesstokens in reference and not posts. Am I thinking right?
var UserSchema = new Schema({
username: {
type: String,
unique: true,
required: true,
lowercase: true,
trim: true
},
encrypted_password: {
type: String,
required: true
},
salt: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true,
lowercase: true,
trim: true
},
mobile: {
type: Number,
unique: true
},
bio: {
type: String
},
created: {
type: Date,
default: Date.now
},
access_tokens: [{type: Schema.Types.ObjectId, ref: 'AccessToken'}],
posts: [{type: Schema.Types.ObjectId, ref: 'Post'}]
}, { collection: 'users' });
You should have a separate collection for Posts but you should keep the access_tokens within the user schema. One good reason you might consider separating the posts into its own collection is there are many use cases where you will query for just posts. However, with access_tokens, they will always be tied to a user.
tldr;
Posts should have their own schema
Access tokens should be in user schema
Hope that helps!

Resources