Mongoose / MongoDB User notifications scheme suggestions - mongodb

I was wondering what is the best scheme for user/notifications kind of scenario like the following :
You have multiple users.
You have multiple notifications that might be for a single user, for some users or for all users.
You need a notification "read" entry in the storage, to know if the user has read the notification or not.
Option One
Embedded notifications scheme
Notifications = new Schema ( {
message : String,
date : { type : Date, default: Date.now() }
read : { type: Boolean, default : false }
});
User = new Schema( {
username : String,
name : String,
notifications : [Notifications]
});
Pros :
It is very easy to display the data, since calling User.find() will display notifications as array object.
Cons :
When you create a notification for every user, you need to do .push to every embedded Notifications
Multiple notifications entries for every user ( multiple data in the database )
Giant embedded document ( I read something about <4MB limit of those )
Since it is a Embedded Document - ( mongoose DocumentArray ) you can't search or skip. You load every notifications everytime you access user.
Option Two
Populate (DBRef like) objects
Notification = new Schema ({
message : String,
date : { type : Date, default : Date.now() }
});
UserNotification = new Schema ({
user : { type : Schema.ObjectId, ref : 'User' },
notification : { type : Schema.ObjectId, ref : 'Notification' },
read : { type : Boolean, default : false }
});
User = new Schema( {
username : String,
name : String,
notifications : [ { type : Schema.ObjectID, ref : 'UserNotification' } ]
});
Pros :
Optimal for queries
No duplicate data
Supporting large amount of notifications
Cons :
You have 3 collections, instead of one in ( Option One has only one collection )
You have 3 queries every time you access the collection.
Questions
What do you think is the best scheme from those two?
Am I missing something or some kind of basic NoSQL knowledge?
Can someone propose better scheme?
Thank you, in advance and I'm sorry for the long post, but I think I can't explain it simpler.

Option 1 looks like it will probably result in a lot of excessive document growth and moves, which would be bad for performance, since most of your writes will be going to the embedded doc (Notifications).
Option 2 I'm not totally clear on your strategy - it seems redundant to have those 3 collections but also embed a list of notifications by objectId if you are already referencing user by ID in the notifications table. You could index on user in Notifications table, and then eliminate the nested array in the Users table.
(EDIT)
Here's another strategy to consider.
Three collections that look like this:
Users:
_id: objectid
username : string
name: string
Notifications:
_id: objectid
to (indexed): objectid referencing _id in "users" collection
read: boolean
Global Notifications:
_id: objectid
read_by: [objectid referencing _id in users]
For notifications meant for a single user, insert into Notifications for that user. For multiple users, insert one for each user (alternately, you could make the "to" field an array and store _ids of all the recipients, and then maintain another list of all the recipients who have read it). To send a notification to all users, insert into Global Notifications collection. When the user reads it, add their user's _id to the read_by field.
So, to get a list of all the unread notifications of a user, you do two queries: one to notifications, and one to global notifications.

// namespace like a channel
// we have a notification from specific channel or namespace
const NamespaceSchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
author: {
type: String,
required: true
},
createdAt: {
type: Boolean,
default: Date.now()
},
notifications: [{
type: Schema.Types.ObjectId,
ref: 'Notification',
}]
});
// the notification schema have a subscribers to specific notification (objectId)
//
const NotificationSchema = new Schema({
title: {
type: String
},
message: {
type: String
},
read: {
type: Boolean,
default: false
},
subscribers: [{
type: Schema.Types.ObjectId,
ref: 'Subscriber'
}],
createdAt: {
type: Date,
default: Date.now()
}
});
// subscribers subscribe to a namespace or channel
const SubscriberSchema = new Schema({
subscriber: {
type: String,
required: true
},
namespaces: [{
type: Schema.Types.ObjectId,
ref: 'Namespace',
}]
});

Related

MongoDB / Mongoose schema for users

I start by saying I'm a beginner with mongodb, but I'm developing a webapp just to study this db in more depth.
I can't "think" of a valid solution (at the schema level), which allows me to manage users and the data associated with them in this case:
I have three types of users: "basic user", "user manager" and "supervisor".
The basic user must be able to see only his data, the “user manager“ must be able to see his data and the data of the users under him. In cascade, the supervisor user must be able to see his data, those of the manager and users under him.
-Practical example:
a user "X" has been assigned tasks, if user "X" has a manager "R", the manager must see his tasks and the tasks of "X". In cascade, if the manager has a supervisor, the supervisor must be able to see his tasks, those of "R" and those of "X."
Any advice or example?
Many thanks
You can create one collection to store the user details.
Example - UserSchema
email: {
type: String,
unique: true,
required: true
},
userId: {
type: String,
unique: true,
required: true
},
managerId: {
type: String,
required: false
},
superVisorId: {
type: String,
required: false
},
name: {
type: String
},
roleId: {
type: Array,
required: true,
enum: [EMPLOYEE, MANAGER, SUPERVISOR],
default: [EMPLOYEE]
}
Here userId is something which you can assign uniquely to each user.
Now you can write some middleware which checks the roleId when a user is trying to access data of say -> EMPLOYEE is trying to access data of MANAGER based on the roleId you can reject this action.
You can have another collection taskSchema
taskId: {
type: String,
required: true,
unique: true
},
taskDescription: {
type: String
},
// Store the userId from userSchema,
// This refers to whom this particular task is assigned to.
taskAssignedTo: {
type: String,
required: true,
unique: true
}
Now, If the SUPERVISOR want's to access all the tasks that have been assigned to them You can simply run a query to first fetch all the MANAGERS and then fetch all the EMPLOYEE under that manager and store them.
Now just fetch all the tasks assigned to each of these userIds.

MongoDB joining user and likes collection based on reference ids

I am trying to create a little social network using ExpressJS and MongoDB. I have a little problem relating to likes and posts collection. I know you can embed a likes inside a posts collection, but I have decided to separate both of the collection and use reference ids so I can join them later on. The main problem I have currently is this, how do I include the likes reference on the posts collection?
Let's say my posts schema looks something like this:
const PostSchema = new Schema({
content: { type: String, required: true },
isLiked: false,
}, { timestamps: true });
and my likes schema looks something like this:
const LikeSchema = new Schema(
{
// The user who is liking the post.
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
// The post that is being liked.
question: {
type: Schema.Types.ObjectId,
ref: 'Question',
required: true
},
},
{ timestamps: true }
);
I wanna make it so that whenever I try to query the posts collection, I can also get the likes embedded in it by referencing the collection and not modifying the schema to have embedded likes in it.
An example response:
{
_id: ObjectId("test"),
content: 'A post',
isLiked: false,
likes: ["A user object here based on the `likes collection`"]
}
You have to obtain them before sending the response:
Find all the likes of that post, something similar to Like.find({ question: <postId> })
Then you can resolve the users of that likes, in the command above you can concatenate .populate('user') with the mongoose populate feature
If you are interested only to the user object and not the entire like object, you can extract resolved user: const users = likes.map(x => x.user)
Then you can add the users array to the post object and sending the final object as response

Database design - saving the entire object to a user or just the id of an object?

database noob here using MongoDB, in my program, I have users, and the core of my program are these roadmaps that I display. So, each user can create roadmaps, save others roadmaps, blah blah... Each user has a field named savedRoadmaps and createdRoadmaps which should store the roadmaps. My question is, should I just store the roadmap _ids in the savedRoadmap and createdRoadmaps field or the entire roadmap?
I am asking this because it feels like saving just the _id of the roadmaps can save storage, but it might not come in handy when I have to fetch the data of the user first, then fetch the roadmap using the roadmap ID in the user's savedRoadmap/createdRoadmap field, versus just fetching the user and the savedRoadmap field will already have the roadmap in there.
And btw, is there any sweet and brief database design read out there, please direct me to some if you know any!
For a user, I want it to have a name, email, password, description ofcourse, and also savedRoadmaps and createdRoadmaps. A user can create unlimited roadmaps and also save as much as he or she wants. For a roadmap, I want it to have a name, category, time_completion, author, date, and a roadmap object which will contain the actual json string that I will use d3 to display. Here's my User and Roadmap Schema right now:
const RoadmapSchema = new Schema({
author: {
type: String,
require: false
},
name: {
type: String,
require: true
},
category: {
type: String,
require: true
},
time_completion: {
type: Number,
require: true
},
date: {
type: Date,
default: Date.now
},
roadmap: {
type: "object",
require: true
}
});
and User Schema:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
savedRoadmap: {
type: "object",
default: []
},
createdRoadmap: {
type: "object",
default: []
}
});
My question is, inside of the savedRoadmap and createdRoadmap fields of the User schema, should I include just the _id of a roadmap, or should I include the entire json string which represents the roadmap?
There are 3 different data-modeling techniques you can use to design your roadmaps system based on the cardinality of the relationship between users and roadmaps.
In general you need to de-normalize your data model based on the queries that are expected from your application:
One to Few: Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
One to Many: Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
One-to-Squillions: Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
And btw, is there any sweet and brief database design read out there,
please direct me to some if you know any!
Rules of Thumb for MongoDB Schema Design: Part 1

Cannot set the reference to the user document in courses mongoose

I am currently working on a RESTful API, and I am trying to reference the users schema in the courses document such that, when a POST request gets sent to the route of the course, a course is created in the DB and has as one of its fields a reference to the user that created it. However, for the life of me, I cannot figure out why the "user" field is not appearing when I post. There seem to be quite a few of these questions here on Stack so I may just be adding to the pile, but I tried their solutions and they did not work for me
var mongoose = require('mongoose')
var Schema = mongoose.Schema
var userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
var CourseSchema = new Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'}, //FOR some reason this is not showing up on any courses created using the
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
estimatedTime: {
type: String
},
materialsNeeded: {
type: String
}
});
var User = mongoose.model('User', userSchema);
var Course = mongoose.model('Course', CourseSchema);
module.exports = {Course, User};
Do you see anything in here that would preclude the user field from appearing when a new course is created?
I have attached some screenshots to further explain.
This first image is a screen of the currently authenticated user credentials (fake data obviously). This is the user that is sending the POST request for the new course. I would expect his information to be attached to the course (see screenshot 3)
This image shows the body of the request that is sent. You can see that the key-value pairs match what is in the CourseSchema. I would expect that the "user" field would be created once the POST request is sent.
This last image is some dummy data that is the expected result.
Thanks all for taking a look at this!
User field will not be automatically added to the course document. You have to manually set the user field in the request body itself or while creating a course.
Example of the course body to be sent:-
{
user: "userId",
title: "test",
description: "test",
estimatedTime: "test",
materialsNeeded: 1
}
Also, the result of this will not include the whole user document as you have mentioned in the expected result. It will only return the userId. However, while accessing the course you can populate the user field to get the whole user document. Example for the same
Course.find({...query}).populate("user")

MongoDB Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.