Skip to content

Modeling Roles in MongoDB

For our authorization logic to function, the assigned role must be persistently tracked inside the database. When a user successfully executes the login validation cycle, Passport fetches the entire User document and attaches it directly to the req.user object payload.

By adding the role directly into the document layout, the authorization validation effortlessly occurs entirely in memory by instantly referencing req.user.role.

We implement this utilizing a native Mongoose String definition, heavily restricted by an explicit enum array mapping to our three target application roles.

data/users.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
passwordHash: { type: String, required: true },
// NEW ADDITION: The Role Enum
role: {
type: String,
// The enum restricts the allowed values exclusively to this specific array
enum: ["USER", "MODERATOR", "ADMIN"],
// We default new registrations to the safest, lowest tier
default: "USER",
},
},
{ timestamps: true },
);
const User = mongoose.model("User", UserSchema);
class UserOps {
// ... existing auth methods ...
}
module.exports = new UserOps();

Professor Solo: The enum acts essentially as a rigid database-level spellchecker. If you attempt executing a query modifying a user’s role string to "administrater" or "EDITOR", Mongoose will violently throw a ValidationError exception, blocking the operation entirely.

For 99% of straightforward web applications, assigning a singular, explicit role string (role: 'MODERATOR') exclusively manages the operational requirement impeccably.

If your requirements aggressively expand, requiring intersecting segmented responsibilities (e.g. they need to be a “Forum Moderator” AND a “Finance Admin”), the architecture easily transitions toward utilizing an explicit String array. However, we do not need that complexity here.

T.A. Watts Note: Do not over-engineer an array of roles before you demonstrably require them. Start small and simple. A single string is vastly easier to evaluate in middleware than iterating through an array.

With the data structurally ready to support our matrix, we will construct the protective middleware defending the actual Express routes.

The database knows who is who. Before we build our bouncers, let’s build a unified dashboard so we can actually manage these accounts!