1

I'm setting up a Mongo database in Express with Mongoose and I'm trying to decide how to model the users. I've never modeled multiple users in the MEAN stack before and thought I'd reach out for some best-practices - I'm an instructor and need to be able to teach my students best practices. I haven't been able to find a whole lot out there, but perhaps I'm searching for the wrong things.

The app will have 3 user types, student, staff, and admin. Each user type will require some of the same basics - email, password, first and last names, phone, etc. If the user is a student, they will need to provide additional info like their high school name, grade, age, gender, etc, which ideally will be required.

This is what I've come up with so far - a single user model that requires all the basic information, but also has schema set up to allow for the additional information that students will need to include. Then I also have a pre-save hook set up to remove the "studentInfo" subdocument if the user being saved doesn't have a "student" role:

var mongoose = require("mongoose");
var Schema = mongoose.Schema;

var ethnicityList = [
    "White",
    "Hispanic or Latino",
    "Black or African American",
    "Native American or American Indian",
    "Asian / Pacific Islander",
    "Other"
];

var userSchema = new Schema({
    firstName: {
        type: String,
        required: true
    },
    lastName: {
        type: String,
        required: true
    },
    phone: {
        type: Number,
        required: true
    },
    email: {
        type: String,
        required: true,
        lowercase: true,
        unique: true
    },
    password: {
        type: String,
        required: true
    },
    preferredLocation: {
        type: String,
        enum: ["provo", "slc", "ogden"]
    },
    role: {
        type: String,
        enum: ["student", "staff", "admin"],
        required: true
    },
    studentInfo: {
        school: String,
        currentGrade: Number,
        ethnicity: {
            type: String,
            enum: ethnicityList
        },
        gender: {
            type: String,
            enum: ["male", "female"]
        }
    }
}, {timestamps: true});

userSchema.pre("save", function (next) {
    var user = this;
    if (Object.keys(user.studentInfo).length === 0 && user.role !== "student") {
        delete user.studentInfo;
        next();
    }
    next();
});

module.exports = mongoose.model("User", userSchema);

Question 1: Is this an okay way to do this, or would it be better just to create two different models and keep them totally separate?

Question 2: If I am going to be to restrict access to users by their user type, this will be easy to check by the user's role property with the above setup. But if it's better to go with separated models/collections for different user types, how do I check whether its a "Staff" or "Student" who is trying to access a protected resource?

Question 3: It seems like if I do the setup as outlined above, I can't do certain validation on the subdocument - I want to require students to fill out the information in the subdocument, but not staff or admin users. When I set any of the fields to required, it throws an error when they're not included, even though the subdocument itself isn't required. (Which makes sense, but I'm not sure how to get around. Maybe custom validation pre-save as well? I've never written that before so I'm not sure how, but I can look that up if that's the best way.)

1
  • If you teach CS, you may be interested in the new CS Educator's Stack Exchange (though since it's still in private beta, it's easiest to enter through here) Commented Jun 2, 2017 at 19:12

1 Answer 1

2

Well, Here are my two cents.

  1. You would be better off creating separate schema models and then injecting the models on a need to basis.

for e.g. If I have a blog schema as follows:

        var createdDate = require('../plugins/createdDate');

    // define the schema
            var schema = mongoose.Schema({
                title: { type: String, trim: true }
              , body: String
              , author: { type: String, ref: 'User' }
            })

// add created date property
schema.plugin(createdDate);

Notice that author is referring to User and there is an additional field createdData

And here is the User Schema:

var mongoose = require('mongoose');
var createdDate = require('../plugins/createdDate');
var validEmail = require('../helpers/validate/email');

var schema = mongoose.Schema({
    _id: { type: String, lowercase: true, trim: true,validate: validEmail }
  , name: { first: String, last: String }
  , salt: { type: String, required: true }
  , hash: { type: String, required: true }
  , created: {type:Date, default: Date.now}
});

// add created date property
schema.plugin(createdDate);

// properties that do not get saved to the db
schema.virtual('fullname').get(function () {
  return this.name.first + ' ' + this.name.last;
})

module.exports = mongoose.model('User', schema);

And the created Property which is being refereed in both User and Blogspot

// add a "created" property to our documents
module.exports = function (schema) {
  schema.add({ created: { type: Date, default: Date.now }})
}

If you want to restrict access based on the user types, you would have to write custom validation like in the User schema we had written for emails:

var validator = require('email-validator');

module.exports = function (email) {
  return validator.validate(email);
}

And then add an if-else based on whatever validations you do.

2 and 3. So, Yes custom validations pre-save as well.

Since you are an instructor I preferred to just point out the practices that are used instead of elaborating on your specific problem.

Hope this helps! :)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.