Build a Secure Login System with Express.js and MongoDB — Featuring Roles, Permissions, and Dual Dashboards
Ever built a login page… only to realize you have no idea where to go from there?
We've all been there. You just got your shiny "Login" and "Register" buttons working on your first Node.js app. But suddenly, you’re hit with questions like:
"How do I separate the admin dashboard from normal users?"
"Where do I store roles and permissions?"
"Wait… how do I even organize my files properly?"
Trust me, I learned this the hard way — with messy folders, spaghetti code, and lots of 401 errors. 😩
So, let’s do this right.
In this post, I’ll walk you through building a solid Express.js + MongoDB login/register system, complete with:
- 📂 A clean folder structure
- 🧑💼 Two login systems (Admin and User)
- 🔐 Role-based access control (RBAC)
- 🧭 Two separate dashboards
- ⚙️ A centralized admin panel and public-facing website
Let’s dive in step-by-step — no jargon, no overwhelm.
🧱 Folder Structure That Actually Makes Sense
Before we write any code, let’s organize our project like a pro:
project-root/
│
├── controllers/
│ ├── authController.js
│ ├── adminController.js
│ └── userController.js
│
├── middlewares/
│ └── authMiddleware.js
│
├── models/
│ ├── User.js
│ └── Role.js
│
├── routes/
│ ├── authRoutes.js
│ ├── adminRoutes.js
│ └── userRoutes.js
│
├── utils/
│ └── token.js
│
├── config/
│ └── db.js
│
├── app.js
└── server.js
📌 Pro Tip: A clean structure saves your future self hours of frustration when debugging or adding features.
🧑💻 Step-by-Step: Login, Register, Roles & Permissions
Let’s build it in layers. Follow along 👇
🔗 1. Connect to MongoDB
config/db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB Connected');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
module.exports = connectDB;
👤 2. User and Role Models
models/User.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
password: String,
role: { type: String, enum: ['admin', 'user'], default: 'user' }
});
module.exports = mongoose.model('User', userSchema);
models/Role.js (Optional: for custom permissions)
const mongoose = require('mongoose');
const roleSchema = new mongoose.Schema({
role: String,
permissions: [String]
});
module.exports = mongoose.model('Role', roleSchema);
🛂 3. Auth Controller: Register & Login
controllers/authController.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
exports.register = async (req, res) => {
const { name, email, password, role } = req.body;
const hashed = await bcrypt.hash(password, 10);
const user = new User({ name, email, password: hashed, role });
await user.save();
res.status(201).json({ message: 'User registered' });
};
exports.login = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !await bcrypt.compare(password, user.password)) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET);
res.json({ token, role: user.role });
};
🔐 4. Middleware: Role-Based Auth
middlewares/authMiddleware.js
const jwt = require('jsonwebtoken');
exports.protect = (roles = []) => {
return (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.sendStatus(401);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!roles.includes(decoded.role)) return res.sendStatus(403);
req.user = decoded;
next();
} catch {
res.sendStatus(401);
}
};
};
🚦 5. Routes for Auth and Dashboards
routes/authRoutes.js
const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');
router.post('/register', register);
router.post('/login', login);
module.exports = router;
routes/adminRoutes.js
const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/authMiddleware');
router.get('/dashboard', protect(['admin']), (req, res) => {
res.json({ message: 'Welcome to the Admin Dashboard' });
});
module.exports = router;
routes/userRoutes.js
const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/authMiddleware');
router.get('/dashboard', protect(['user']), (req, res) => {
res.json({ message: 'Welcome to the User Dashboard' });
});
module.exports = router;
🖥️ Admin Panel vs. Website
You can keep the admin panel under a subdomain like admin.myapp.com, and the regular user website as www.myapp.com.
Your front-end just needs to hit the respective dashboards:
- Admin:
GET /admin/dashboard - User:
GET /user/dashboard
You control what the user sees by checking their role from the decoded JWT.
✅ Wrapping Up: Why This Structure Rocks
- ✅ Easy to maintain and scale
- ✅ Secure, role-based access
- ✅ Two dashboards = clear separation of concerns
- ✅ Cleaner code = happier dev
You’re not just building a login system — you’re building a real-world, production-ready system.
🙋♂️ Beginner FAQ
Q1: Why separate dashboards for admin and user?
Because admins usually need tools like user management, analytics, etc., which regular users don’t need to see.
Q2: Can I store more roles?
Yes! Add roles like moderator, editor, etc., and update the middleware checks.
Q3: What if I need custom permissions?
Use a separate Role model with permission arrays and check them in middleware.
Your Turn!
Have you ever struggled with setting up a login system that supports both admins and regular users? What was the trickiest part?
👇 Drop your story or tip in the comments — let’s learn together!
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Python Flask Blog Project with Admin Panel
Are you ready to launch a powerful, modern, and fully customizable blog? Look no further! Introducing the Flask-Blog Pro...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.