Build Express.js APIs for Admin Panel and Website: Keep Admin & User Worlds Separate (and Clean!)

Author

Kritim Yantra

Aug 04, 2025

Build Express.js APIs for Admin Panel and Website: Keep Admin & User Worlds Separate (and Clean!)

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


🧠 Why Two APIs?

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.


🧱 Folder Structure That Works

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

πŸ› οΈ Step-by-Step Setup

1. Connect MongoDB

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;

2. Create a Simple User Model

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);

3. Auth Middleware to Protect Routes

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);
    }
  };
};

4. Create Admin API Routes

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' });
};

5. Create User API Routes

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);
};

6. Combine Routes in Your App

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}`);
});

πŸ§ͺ Sample Requests (Postman Friendly!)

  • πŸ” Admin Panel

    • GET /api/admin/users β€” List all users
    • DELETE /api/admin/user/:id β€” Delete a user
  • πŸ‘€ User Website

    • GET /api/user/profile β€” Get logged-in user's profile

Use Bearer token headers to test JWT-based routes.


πŸ’‘ Bonus Tips

πŸ” 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 vs api.myapp.com).


πŸ™‹β€β™€οΈ FAQ

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.


What’s Your Setup?

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.

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts