9

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!

Deadlift
  • 116
  • 1
  • 4
  • 1
    I suggest every document is complete. So I do not think it is a good idea to use dbRef (a doc is limit to 16MB, unless your doc will exceed this limitation, do not use dbRef). I think teacher and student should be two collection instead of one. Please tell me why you want to use one collection? – Flying Fisher May 24 '16 at 07:33
  • @FlyingFisher There's no particular reason for using one collection other than trying to take full advantage of a schemaless db. Using references is a more traditional approach taken with RDBMS and I was just wondering if there was a newer, more modern approach. So would option A be the best route...minus the dbRef...and just adding a user_id to the student and teacher schemas? Thanks for your input. – Deadlift May 24 '16 at 17:34
  • 1
    For my experience of using MongoDB, the document completeness is more important than data redundancy. So I suggest student contain more data about teacher not just _id. For one purpose, when you get one document, everything you need is in it. e.g. When you show student info, no need to fetch teacher collection, when you show teacher info, no need to fetch student collection. – Flying Fisher May 25 '16 at 00:30
  • is discriminators a good solution for this ? – Diaa Eddin Feb 03 '21 at 10:42

2 Answers2

0

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.

0

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