Kritim Yantra
Aug 04, 2025
Ever tried building a web app and ended up with admin and user routes all jumbled together in one file?
Yeah, me too. π
It starts out fine β until you're 100+ lines deep in app.js
, with /login
, /admin/dashboard
, /user/profile
, /admin/settings
, and everything else floating around like a digital junk drawer.
So hereβs the game plan:
Letβs create two clean, separate API systems using Express.js β one for the admin panel, one for the user-facing website.
β
Clean structure
β
Better security
β
Easier to scale
β
Happier future-you
Hereβs the big idea:
Feature | Admin Panel | User Website |
---|---|---|
Access Level | Internal (admins only) | Public (authenticated users) |
Responsibilities | User management, analytics, etc. | Login, signup, user dashboards |
URL Prefix | /api/admin/... |
/api/user/... |
Keeping these separated makes permissions easier, testing cleaner, and scaling smoother.
Hereβs how weβll organize everything:
project-root/
β
βββ controllers/
β βββ admin/
β β βββ adminController.js
β βββ user/
β βββ userController.js
β
βββ routes/
β βββ adminRoutes.js
β βββ userRoutes.js
β
βββ middlewares/
β βββ auth.js
β
βββ models/
β βββ User.js
β
βββ config/
β βββ db.js
β
βββ app.js
βββ server.js
config/db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
} catch (error) {
console.error('Mongo error:', error.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: ['user', 'admin'], default: 'user' }
});
module.exports = mongoose.model('User', userSchema);
middlewares/auth.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/adminRoutes.js
const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/auth');
const { listUsers, deleteUser } = require('../controllers/admin/adminController');
// Only accessible by 'admin'
router.get('/users', protect(['admin']), listUsers);
router.delete('/user/:id', protect(['admin']), deleteUser);
module.exports = router;
controllers/admin/adminController.js
const User = require('../../models/User');
exports.listUsers = async (req, res) => {
const users = await User.find({ role: 'user' });
res.json(users);
};
exports.deleteUser = async (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.json({ message: 'User deleted' });
};
routes/userRoutes.js
const express = require('express');
const router = express.Router();
const { protect } = require('../middlewares/auth');
const { getProfile } = require('../controllers/user/userController');
router.get('/profile', protect(['user', 'admin']), getProfile);
module.exports = router;
controllers/user/userController.js
const User = require('../../models/User');
exports.getProfile = async (req, res) => {
const user = await User.findById(req.user.id).select('-password');
res.json(user);
};
app.js
const express = require('express');
const app = express();
const adminRoutes = require('./routes/adminRoutes');
const userRoutes = require('./routes/userRoutes');
const connectDB = require('./config/db');
require('dotenv').config();
connectDB();
app.use(express.json());
app.use('/api/admin', adminRoutes);
app.use('/api/user', userRoutes);
module.exports = app;
server.js
const app = require('./app');
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
π Admin Panel
GET /api/admin/users
β List all usersDELETE /api/admin/user/:id
β Delete a userπ€ User Website
GET /api/user/profile
β Get logged-in user's profileUse Bearer token headers to test JWT-based routes.
π Pro Tip: Never share admin routes with your frontend. Keep them behind an internal admin dashboard UI.
π§© Scalability Tip: Later, you can host admin APIs on a different subdomain or server entirely (
admin.api.myapp.com
vsapi.myapp.com
).
Q1: Can I use the same login route for both admin and user?
Yes! Just assign roles (admin
, user
) when creating accounts and check them in middleware.
Q2: How do I protect the admin dashboard from users?
Use the protect(['admin'])
middleware β it filters access based on roles in the JWT.
Q3: Is it okay to mix admin and user APIs in one file?
You can, but separating them makes your code cleaner, safer, and more scalable.
How are you handling admin vs. user APIs in your app? Still got everything lumped into one giant file? π Or did you build a beautiful, separate system?
π Drop your thoughts, setups, or struggles in the comments β letβs help each other grow.
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google