MongoDB best practise - One to many relation - javascript

According to this post I should embed a "reference": MongoDB relationships: embed or reference?
User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
createdEvents: ['Event']
});
module.exports = mongoose.model('User', userSchema);
Event.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
date: {
type: Date,
required: true
}
});
module.exports = mongoose.model('Event', eventSchema);
So an embedded event looks like this in the database:
My Code works but im curious if this is the right way to embed the event. Because every example for one to many relations is made with references and not embedded.

From my experience, using embedding or referencing depends on how much data we are dealing with.
When deciding on which approach to pick, You should always consider:
1- One-To-Few: if you'll have a small number of events added to a user over time, I recommend you to pick the embedding approach as it is simpler to deal with.
2- One-To-Many: if new events are frequently added, you totally should go for referencing to avoid future performance issues.
Why?
When you are frequently adding new events, if they are being embedded inside an user document, that document will grow larger and larger over time. In the future you will probably face issues like I/O overhead. You can catch a glimpse of the evils of large arrays in the article Why shouldn't I embed large arrays in my documents?. Although it's hard to find it written anywhere, large arrays in MongoDB are considered a performance anti-pattern.
If you decide to go for referencing, I suggest reading Building with Patterns: The Bucket Pattern. The article can give you an idea on how to design your user_events collection in a non-relational way.

Related

How to best reference another schema in a Mongoose schema property?

I am defining a mongoose User schema.
It will contain a reference to an Address schema:
const AddressSchema = mongoose.Schema({
street: String,
city: String,
zip: String,
});
const UserSchema = mongoose.Schema({
...
});
I see from the docs I can do both:
const UserSchema = mongoose.Schema({
name: String,
address: {
type: AddressSchema,
},
...
});
or
const Address = mongoose.model("Address", AddressSchema);
const UserSchema = mongoose.Schema({
name: String,
address: {
{
type: mongoose.Schema.Types.ObjectId,
ref: "Address"
}
},
...
});
I suppose the only difference is that when querying a User document in the first case I always get address populated, while in the second one I will have to populate() it...
I'm asking just to be sure this is the difference, and if there are subtler ones I should take care of... From the docs I couldn't tell...
It really depends on the type of queries you'll need.
If you only need the inner ones in connection to the outer, like the user's private account actions that are supposed to be shown only to him - the subdocument wat is a better way since it doesn't need to populate.
If you will need to show a list of all actions together it'll be a pain to loop over each user to get their actions, then sum it up, and then you'll have that array, so the better way here is what in SQL called "normalization", you'll end up with one collection of all users so you will be able to display it, but each action will have a reference to the user so you can filter it by the specific user

How to implement many to many relationship in mongoDb?

I am working on a photography blog, in which I need to store images according to the tags provided by the user. I tried googling and came up with implementing a many to many relationship in mongoDb , but I am still confused as how to take multiple inputs from the user and store them separately . I tried something like this but I don't know how to proceed further.
This is my picture model:-
const mongoose = require("mongoose");
const snapSchema = new mongoose.Schema({
Caption: {
type: String,
required: "Caption cannot be blank."
},
image: String,
imageId: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
tags: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tag"
}
]
});
module.exports = mongoose.model("Snap", snapSchema);
This is my tag model:-
const mongoose = require("mongoose");
const tagSchema = new mongoose.Schema({
text: String,
snaps: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Snap"
}
]
});
module.exports = mongoose.model("Tag", tagSchema);
If you think about it, usually an Image can have multiple Tags, not the other way around. I wouldn't associate Images with Tags directly. If you want to present images tag-wise, you simply filter your images by tags associated with them. So, your Snap model is perfect, but your Tag does not need association with any Image (it can live without an image, right?).
For presentation purposes, you do a simple filter:
Order.find( { tags: your_tag_id } )
Here's a great example of how to query by array content: https://docs.mongodb.com/manual/tutorial/query-arrays/
As for your update scenario, I imagine that user can input multiple tags for a single image, right? In that case, you simply insert relevant tag ids to tags image field.

Users schema with poking other users schema in node.js

I am making an application in which a user can poke other users. Here is the code for the schema designs I have considered. The first is using only a users schema:
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
Another way is using a pokes schema. Here I'm storing the object ids of the pokes schema in the users schema.
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
In the third way I totally remove the relation between the two schemas:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
In the second and third ways I can query for pokes easily.
I want to know which of the three is the best design and why. Also if userA pokes userB then it can be the case that userB can also poke back to userA. I'm learning node.js currently and am confused about the design in mongoDb.
Alright, so here's the best I can do. You briefly answered my question in my comment above but I'd like to point out it's important to think about what you are doing (or expect to be doing) more and how much more. That aside though, let's take a look at each schema.
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
When we look at this first one it seems inadequate for your needs. It's a collection of users who have a name and an array of pokes they have made. If we need to know who a user has poked then that's a really easy and fast query - it's right there under their name, search by name or _id and we're done! But what happens when you want to look up who has poked this user? You will need to query every single user and then search through every single pokes array for the original user. If we have m-many users and each has n-many pokes, that's doing m* n tests. Yikes. If m and n get big that's going to be a lot (think of the difference of 100 * 100 vs 10,000 * 10,000 or even more!). Even if you personally are not coding that search in your node then mongo is doing that search. So unless you're sure that looking up who has poked a user is going to be something that is pretty rare this is probably not a good option. Moving on:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
Now we have a pokes schema, nice! If we wanted to do the search we discussed above we can instead query pokes directly based on to_user_id, and then if we need a name of all the users who initiated the pokes we can just query the users based on its _id. Not bad! We also still have the fast way to get the reverse, aka search for pokes a user has initiated, because there is still pokes in our user schema. What happens when a poke occurs, though? We have to update both schemas. So not only will we do a (relatively easy) insert into pokes, we will have to also update our pokes array of the user who did the poking. This might not be so bad, but what happens if one update fails? Now our data is inconsistent - users and pokes don't match. We're also doubling our updates every poke. This might not be a big deal, and if we're getting a user's pokes much more than we're poking then it might be an ok trade-off, but it becomes a little riskier because we've introduced somewhere we can be inconsistent. Alright, last one:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
First, note that these schemas are still related - the pokes schema reference users. It's just not doubly-related like the last one. Anyway, now we've removed the pokes array from the user schema. Ok! We don't run the risk of having inconsistent data anymore, noice! We're also not doing two updates for every poke, toit! The trade-off is now when we want to get the list of users a user has poked we have to do a similar query to the one we did above when we wanted to get a list of users a user has been poked by. Which isn't so bad, but is certainly not as fast as having the pokes array already sitting there and waiting.
In my opinion, unless you're searching for who users have poked (and not been poked by) significantly more often than doing anything else this third scenario is best. The schemas make logical sense, you're not updating twice. It's what I would use. But as I said, it's very important to consider your particular need and design.
Hope that helps!

Setting up an array of collections in MongoDB using Node.js

I am creating a game where, as part of a collection named Game, I am trying to label one of the elements of it. The element in question is supposed to be an array of userNames from another collection. I can't seem to figure out how to access that. Here is what I have in the games collection:
var mongoose = require('mongoose');
var schema = mongoose.Schema;
var ObjectId = schema.ObjectId;
module.exports.Game = mongoose.model('Game', new schema({
id: ObjectId,
gameRoomName: { type: String, required: '{PATH} is required.' },
players: { }
}));
The users collection:
var mongoose = require('mongoose');
var schema = mongoose.Schema;
module.exports.users = mongoose.model('Users', new schema({
userName: {type: String, required: '{PATH} is required.'}
}));
Basically, the usernames for a game will be saved in the Users schema. Then, I'd like to access that and insert it into the Game schema in the players space. I'm imagining it to be something like {type: collection.users}, however, that doesn't seem to be doing the trick.
You can store players as the array of references to Users model
module.exports.Game = mongoose.model('Game', new schema({
.
.
players: [{type: Schema.Types.ObjectId, ref: 'Users'}],
)}
Access later by:
Game.find()
// filter 'players' field
.select('players')
// populate players with only 'username' field
.populate('players', 'username')
.exec(function(err, username) {
// anything with players
});
Long story. You will be good to go after finishing the article
There's a few ways to solve the situation here...but ultimately I think it depends on what data you have readily available at the time you want to add the users into the game object and also how you want to retrieve the data when you need it.
If you have all the usernames cached, whether as objects or just the username itself, it would be more efficient to just add them into the game object.
Example:
var usernamesExample = ["Mike", "Ike", "Clara", "Joe"];
Game.findById(gameIdExample, function(error, foundGame){
// handle errors/checks/etc.
foundGame.players = usernamesExample;
foundGame.save();
})
I personally think this approach is best performance wise. Then again it might not work for your situation, in which case I would need further clarification into how you obtain the username data for the game.

Unique Fields in Mongoose

I'm trying to enforce uniqueness by field on a mongoose model, in an existing database.
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
}
})
Then when I create a user with an email that is already assigned, it passes, and I have 2 users with the same email.
I have read all related SO answers > 2015 and all about dropDups, which is deprecated.
I think I fixed this issue by manually running
db.users.createIndex({email:1}, {unique:true})
However this obviously becomes cumbersome both in development and in production, especially considering the mongoose docs state that the attribute takes care of it:
unique: boolean, whether to define a unique index on this property.
Can someone provide a clear solution to enforcing uniqueness by field on a Mongoose model, considering an existing database and a fresh collection? Thanks
Two user has been created before unique index has been created.So you can insert two users with same email.You can try code below, it make sure all index has been created:
UserModel.on('index', function(error) {
const user1 = new UserModel({
email: '3489'
});
const user2 = new UserModel({
email: '3489'
});
user1.save(console.log);
user2 .save(console.log);
});

Categories

Resources