Kritim Yantra
Aug 04, 2025
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:
Let’s dive in step-by-step — no jargon, no overwhelm.
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.
Let’s build it in layers. Follow along 👇
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;
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);
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 });
};
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);
}
};
};
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;
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:
GET /admin/dashboard
GET /user/dashboard
You control what the user sees by checking their role from the decoded JWT.
You’re not just building a login system — you’re building a real-world, production-ready system.
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.
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!
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google