MongoDB/ Mongoose many to many relationship - user, project and role - mongodb

Our project has following requirements-
User can be part of multiple projects
A project can have multiple users.
User can be either Owner or Admin or Viewer of a project.
Obviously there can only one Owner of a project.
There can be multiple Admin or Viewer of a project.
Every user has a home/default project.
So I have created the following schemas - But I am not able to use populate and I find adding/ updating/ removing a lot of pain. Also when I query projects I want to show the email id of the users not the objectId but dont want to use email as a ref as user can change their email. Please help me with a better design considering the most popular operations on the schema-
User can create project
Add people to a project
View all of his projects and members
Change role of someone in the project
Remove someone from the project
View the projects and owner and members (emails not object ids) according to their right
The following working schemas are present in separate files user.js and project.js with required export of the models.
var userSchema = mongoose.Schema({
username: {type:String},
password: String,
email: {type:String, required:true, unique: true, index: true},
createdAt: { type: Date, default: Date.now },
firstName: String,
lastName: String,
home_project: {type:Schema.Types.ObjectId, ref:Project},
owner: [{type:Schema.Types.ObjectId, ref:Project}],
admin: [{type:Schema.Types.ObjectId, ref:Project}],
viewer: [{type:Schema.Types.ObjectId, ref:Project}]
});
var projectSchema = mongoose.Schema({
projectName: {type:String, required:true, unique: true, index: true},
createdAt: { type: Date, default: Date.now },
owner: {type:String, ref:User},
admin: [{type:String, ref:User}],
viewer: [{type:String, ref:User}]
});

Related

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")

Mongoose database modeling

I am new to mongoose as well as nosql. I am designing a database which will contain a list of people and each person could have multiple skills - like C, Java, Python. Further the person would have been using the particular skill since a particular time - eg. Since 2010.
I have created a personSchema and a skillSchema. I am not able to figure how to add the "Since" as the since is specific to a person but is also for a particular skill.
I really need the skill to be a separate schema as the list of skills would be used elsewhere.
let personSchema = new mongoose.Schema({
id: { type: String, required: true, unique: true, index: true, dropDups: true},
firstname: String,
lastname: String,
age: Number
mobile: [Number],
skills: [{type: Schema.Types.ObjectId, ref: 'Skill'}]
});
let skillSchema = new mongoose.Schema({
skillName: String
});
Now where to store "since"?
E.g Tom is working on C++ since 2010 - The 2010 is related to both Tom and C++
skills : [
{
skill : {type: Schema.Types.ObjectId, ref: 'Skill'}
since : Number
}]
Adding 'Since' this way will make more sense as each skill reference will have its since value with it.
Hope it helps.

MongoDB schema design for multiple user types

I'm about to build a Node.js+Express+Mongoose app and I'd like to pick the community's brains and get some advice on best practices and going about creating an efficient schema design.
My application is going to include 2 different user types, i.e "teacher" and "student". Each will have a user profile, but will require different fields for each account type. There will also be relationships between "teacher" and "student" where a "student" will initially have 1 teacher (with the possibility of more in the future), and a "teacher" will have many students.
My initial thoughts about how to approach this is to create a general User model and a profile model for each user type (studentProfile model & teacherProfile model), then reference the appropriate profile model inside the User model, like so (A):
var UserSchema = new Schema({
name: String,
email: String,
password: String,
role: String, /* Student or Teacher */
profile: { type: ObjectID, refPath: 'role' }
});
var studentProfileSchema = new Schema({
age: Number,
grade: Number,
teachers: [{ type: ObjectID, ref: 'Teacher' }]
});
var teacherProfileSchema = new Schema({
school: String,
subject: String
});
Or do I just go ahead and directly embed all the fields for both profiles in the User model and just populate the fields required for the specific user type, like so (B):
var UserSchema = new Schema({
name: String,
email: String,
password: String,
role: String, /* Student or Teacher */
profile: {
age: Number,
grade: Number,
school: String,
subject: String
},
relationships: [{ type: ObjectID, ref: 'User' }]
});
The downside to option B is that I can't really make use of Mongoose's required property for the fields. But should I not be relying on Mongoose for validation in the first place and have my application logic do the validating?
On top of that, there will also be a separate collection/model for logging students' activities and tasks, referencing the student's ID for each logged task, i.e.:
var activitySchema = new Schema({
activity: String,
date: Date,
complete: Boolean,
student_id: ObjectID
});
Am I on the right track with the database design? Any feedback would be greatly appreciated as I value any input from this community and am always looking to learn and improve my skills. What better way than from like minded individuals and experts in the field :)
Also, you can see that I'm taking advantage of Mongoose's population feature. Is there any reason to advise against this?
Thanks again!
You could try using .discriminator({...}) function to build the User schema so the other schemas can directly "inherit" the attributes.
const options = {discriminatorKey: 'kind'};
const UserSchema = new Schema({
name: String,
email: String,
password: String,
/* role: String, Student or Teacher <-- NO NEED FOR THIS. */
profile: { type: ObjectID, refPath: 'role' }
}, options);
const Student = User.discriminator('Student', new Schema({
age: Number,
grade: Number,
teachers: [{ type: ObjectID, ref: 'Teacher' }]
}, options));
const Teacher = User.discriminator('Teacher', new Schema({
school: String,
subject: String
}, options));
const student = new Student({
name: "John Appleseed",
email: "john#gmail.com",
password: "123",
age: 18,
grade: 12,
teachers: [...]
});
console.log(student.kind) // Student
Check the docs.
One approach could be the following:
//Creating a user model for login purposes, where your role will define which portal to navigate to
const userSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
name: {type:String,required:true},
password: {type: String, required: true},
email: {type: String, required: true},
role:{type:String,required:true}
},{timestamps:true});
export default mongoose.model("User", userSchema);
//A student schema having imp info about student and also carrying an id of teacher from Teachers Model
const studentSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
age:{type:Number},
grade:{type:String},
teacher:{type:mongoose.Schema.Types.ObjectId,ref:'Teachers'}
},{timestamps:true});
export default mongoose.model("Students", studentSchema);
//A teacher model in which you can keep record of teacher
const teacherSchema = new mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
subject:{type:String},
School:{type:String},
},{timestamps:true});
export default mongoose.model("Teachers", teacherSchema);

Mongodb schema design for task

I am working on a project which allow user to create task and other user(service provider) can provide them quotation and later let the user decide which service provider is suitable for them.
Here is the service schema:
var ServiceSchema = new Schema({
address: String,
quotations:[{
service_provider:{type:mongoose.Schema.Types.ObjectId, ref:'User'},
price: Number,
date: Date
}],
customers :[{type:mongoose.Schema.Types.ObjectId, ref:'User', childPath:'services_need'}],
});
and here is the user schema
var UserSchema = new Schema({
name:String,
password:String,
email:String,
phone:String,
token:String,
services_need:[{type: mongoose.Schema.Types.ObjectId, ref: 'Service'}],
services_provide:[{type: mongoose.Schema.Types.ObjectId, ref: 'Service'}],
});
The question is that is this in best practise? Any other rules of design I break?
Also , do I need to hold the reference of User in Service?
Thanks
You can do sub document relationships like that without any problems.
What do you mean by "do I need to hold the reference in User "? Please elaborate