The most efficient way to handle one to many relationship in MongoDB - mongodb

Let's say I have a one-to-many relationship - A user has many todos. I want to be able to perform the following:
Give me a specific user without the todos
Give me the todos belongs to a specific user
Give me a single todo by id
Add, update, and delete todo
I can tackle it in three ways:
Embedding the todos inside the user document
import { Schema, model } from 'mongoose';
const userSchema = model('User', new Schema({
name: String,
todos: [{ title: String, id: String }],
}));
Use Todo Ref
import { Schema, model } from 'mongoose';
const userSchema = model('User', new Schema({
name: String,
todos: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Todo"
}],
}));
const todoSchema = model('Todo', new Schema({
name: String
}));
Use Todo Id
import { Schema, model, ObjectId } from 'mongoose';
const userSchema = model('User', new Schema({
name: String
}));
const todoSchema = model('Todo', new Schema({
name: String,
userId: ObjectId
}));
What will be the most efficient way to handle this scenario in MongoDB? Adding an index on the userId property in the last solution will make the query faster?

Related

MongoDB - is there a better way to store a list of ObjectIDs?

Say I have a User schema/model and the user has a list of friends. Mongoose wants you to store your list of friends (foreign key / ObjectID type) as an array, right? Which means if I want to find my friend by ID, Mongoose will search the array until it finds the first instance of a friend with the ID I want. That seems really time inefficient no? Is there a better way?
const FriendSchema = new Schema({
username: { type: String, required: true, unique: true },
});
const UserSchema = new Schema({
username: { type: String, required: true, unique: true },
friends: [FriendSchema],
});
Part of what I was looking for is this:
Indexes are what allows you to "iterate" through an array or a field in a collection without having to look at every single one. So to make sure you don't waste time iterating, you can create an "index" on any field and it makes it searchable in a binary-tree structure.
https://docs.mongodb.com/manual/indexes/
Arrays already are made to have the field be a "key" so you wouldn't need to worry about the time complexity of searching an array by the field name of one of its elements.
https://docs.mongodb.com/manual/core/index-multikey/
Use ref to refer to the documents in another schema and call populate to get the referred doc.
// friend.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const FriendSchema = new Schema({
username: { type: String, required: true, unique: true },
});
module.exports = mongoose.model('Friend', FriendSchema);
// user.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: { type: String, required: true, unique: true },
friends: [{ type: Schema.Types.ObjectId, ref: 'Friend' }],
});
module.exports = mongoose.model('User', UserSchema);
const User = require('user.model.js');
User.find(...)
.populate('friends')
.exec()

Refs mongo dont working nestjs with graphql

I believe I am missing something I already tried to add populate but it doesn't work either I don't know what it is and I can't find any examples about relationships
I want to select many items for one user.
In items it references user but in user items[] returns blank and array is empty in mongo
Users Module
imports: [MongooseModule.forFeature([{ name: 'User', schema: UserSchema }])],
UserSchema
export const UsersSchema = new mongoose.Schema({
[...]
items: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Items'}],
});
Items module
imports: [
MongooseModule.forFeature([{ name: 'Item', schema: ItemSchema }]),
],
ItemSchema
export const ItemSchema = new mongoose.Schema({
title: String,
price: Number,
description: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: 'Users'},
});
Mutation to create item in items.resolver.ts
#Mutation(() => ItemType)
async createItem(#Args('input') input: ItemInput): Promise<ItemInput> {
return this.itemsService.create(input);
}
and service with create method
async create(createItemDto: ItemInput): Promise<ItemType> {
const createdItem = new this.itemModel(createItemDto);
return await createdItem.save();
}
Item reference in users only shows an empty array in mongo, but my user reference in items shows object id as below

Is there anyway to remove one Document and then update ref model's ObjectId array automatically?

Here my models.
const UserSchema = mongoose.Schema({
name: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
});
const CommentSchema = new mongoose.Schema({
comment: String,
creatorId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
User could have many comments.
So User and Comment is one-to-many relationship.
When User create a new comment, A new Comment is created and it's ObjectId is pushed to User's comments array.
What I want to do is,
delete Comment's ObjectId automatically when delete one Comment.
I know I can do this with two queries.
Is there anyway to combine these two to one?
await user.comments.remove(comment.id);
await comment.remove();
You can't run remove on two collections / schemas however you can take advantage of Post Middleware and define such middleware on CommentsSchema:
CommentsSchema.post('remove', function(comment) {
await UserSchema.update({ _id: comment.creatorId }, { $pull: { comments: comment._id } })
});

Using Mongoose Populate Virtuals on Arrays

Here is my Follow model
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'Card'
}]
});
module.exports = mongoose.model('Follow', FollowSchema);
Here's my Card model
let mongoose = require('mongoose');
let Schema = mongoose.Schema;
let CardSchema = new Schema({
title: String,
content: String,
likes: Number,
createdById: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Card', CardSchema);
A typical Follow Model in DB will have something like this:
{
"_id" : ObjectId("59f0eef155ee5a5897e1a66d"),
"user" : ObjectId("59e3e617149f0a3f1281e849"),
"following" : [
ObjectId("59e21942ca5652efc4ca30ab"),
ObjectId("59e13b2dca5652efc4ca2cf5")
]
}
Here's what I'm trying to do:
To get all post from those you follow, find user in Follow model, and for each ObjectId in the following array, pull all documents from Card model, matching createdById field.
Using Mongoose Populate Virtuals, how do I do the schema relationship and populate query eventually?
Or, how would I go about creating and querying such a relationship with or without virtuals?

mongoose, populate V.S another query

my case is:
I have postSchema and commentSchema, like this:
const postSchema = new Schema({
comments: [
{
type: Schema.Types.ObjectId,
ref: 'Comment'
}
]
}
const commentSchema = new Schema({
post: {
type: Schema.Types.ObjectId,
ref: 'Post'
}
}
If I know the post._id, I want to query the post documents and comment documents.
I think there are two way:
popluate: Post.findById(post._id).populate('Comment').exec()
start another query:
Post.findById(post._id).exec()
Comment.find({post: new Types.ObjectId(post._id)}).exec()
So, which is better? Or, when to use populate and when to start another query?