DEV Community

Usman Murtaza
Usman Murtaza

Posted on

How I Built Maniesta Campus: A Campus Management System with React and Node.js

Educational institutions run on paperwork and spreadsheets. Student records live in one file, attendance in another, grades somewhere else, and schedules β€” usually β€” in someone's head.

I kept hearing the same complaint from small colleges and coaching centers: "We need something simple that just works." So I built Maniesta Campus β€” a role-based campus management system with separate dashboards for admins, faculty, and students.

πŸ”— Live Demo: maniestacampus.netlify.app

This post walks through how I designed the system, the technical decisions I made, and the parts that were harder than expected.


The Problem

Before writing a single line of code, I mapped out what an actual campus needs:

  • Admins need to manage students, faculty, courses, and enrollments from one place
  • Faculty need to mark attendance, upload grades, and view their class rosters
  • Students need to see their schedule, grades, and enrolled courses without asking anyone

The core challenge was role-based access β€” one login system that serves three completely different user journeys. A student should never see admin controls, and a faculty member shouldn't be able to edit another faculty member's course.

Traditional CRUD apps don't handle this well. You end up with if (user.role === 'admin') scattered everywhere. I wanted something cleaner.


Tech Stack Decisions

Here's what I picked and why:

  • React β€” the UI needed to render completely different dashboards based on role. Component composition made this natural.
  • Node.js + Express β€” a straightforward REST API that I could iterate on quickly. No need for anything heavier.
  • MongoDB β€” campus data is hierarchical and evolves fast. Student records have different fields than faculty records, and course structures change per institution. Flexible schemas saved me from constant migrations.
  • JWT β€” stateless authentication means the API doesn't need session storage. Scales horizontally, and the frontend can hold the token with the user's role embedded in it.
  • Tailwind CSS β€” for fast UI iteration. When you're building three dashboards, you don't want to write custom CSS for every component.

Architecture Overview


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   React Client   β”‚
β”‚  (3 dashboards)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  HTTP + JWT
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Express API     β”‚
β”‚  - Auth routes   β”‚
β”‚  - RBAC middlewareβ”‚
β”‚  - Resource routesβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    MongoDB       β”‚
β”‚  users, courses, β”‚
β”‚  grades, etc.    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Enter fullscreen mode Exit fullscreen mode

Every request flows through an authentication middleware first, then a role-check middleware, then the actual route handler. Nothing reaches the database without passing the role gate.


The Core: Role-Based Access Control

The RBAC system is where most of the design effort went. Here's the middleware that powers it:

// middleware/auth.js
const jwt = require('jsonwebtoken');

// Verify the JWT and attach the user to the request
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // { id, role }
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// Only allow specific roles to proceed
const requireRole = (...roles) => (req, res, next) => {
  if (!roles.includes(req.user.role)) {
    return res.status(403).json({ error: 'Access denied' });
  }
  next();
};

module.exports = { authenticate, requireRole };
Enter fullscreen mode Exit fullscreen mode

Now any route can declare its own access rules cleanly:

// routes/courses.js
const { authenticate, requireRole } = require('../middleware/auth');

// Anyone logged in can view courses
router.get('/courses', authenticate, listCourses);

// Only admins can create courses
router.post('/courses', 
  authenticate, 
  requireRole('admin'), 
  createCourse
);

// Faculty can update grades for their own courses
router.patch('/courses/:id/grades',
  authenticate,
  requireRole('faculty'),
  updateGrades
);
Enter fullscreen mode Exit fullscreen mode

This pattern scales. If I ever add a "teaching assistant" role, I just pass it into requireRole β€” no other code changes.


Frontend: One App, Three Dashboards

On the frontend, I used a simple pattern β€” the auth context holds the user, and the dashboard component switches on the role:

const Dashboard = () => {
  const { user } = useAuth();

  switch (user.role) {
    case 'admin':
      return <AdminDashboard />;
    case 'faculty':
      return <FacultyDashboard />;
    case 'student':
      return <StudentDashboard />;
    default:
      return <Navigate to="/login" />;
  }
};
Enter fullscreen mode Exit fullscreen mode

Each dashboard imports its own set of components. The shared pieces β€” navbar, notifications, profile, logout β€” live outside and are reused across all three.

A protected route wrapper handles the auth check on the way in:

const ProtectedRoute = ({ allowedRoles, children }) => {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" />;
  if (allowedRoles && !allowedRoles.includes(user.role)) {
    return <Navigate to="/unauthorized" />;
  }
  return children;
};
Enter fullscreen mode Exit fullscreen mode

With this in place, adding a new admin-only page is a single line:

<ProtectedRoute allowedRoles={['admin']}>
  <ManageFaculty />
</ProtectedRoute>
Enter fullscreen mode Exit fullscreen mode

Challenges I Didn't Expect

  1. Schema design for multiple user types.
    At first I considered separate collections for students, faculty, and admins. That meant three login flows and three auth systems. Then I realized a single users collection with a role field plus role-specific optional fields is much simpler. When a student is also a teaching assistant, I just update the role β€” no data duplication.

  2. Keeping the JWT payload small.
    I only embed { id, role } in the token. Everything else β€” name, email, permissions β€” is fetched on demand. Bigger tokens are slower to verify, and they become stale as soon as a user updates their profile.

  3. Grade calculation edge cases.
    What counts as an "A"? Every institution has its own grading scale. I ended up making the scale configurable per institution and storing it in the database, so the grading logic stays data-driven rather than hardcoded.


What I Learned

Β· Design RBAC before writing features. Retrofitting access control into an existing app is brutal. Getting it right upfront made everything else easier.
Β· Keep JWTs thin. Only store what the token actually needs to prove identity. Fetch the rest.
Β· Data-driven grading beats code-driven grading. When in doubt, make it configurable.
Β· Component composition in React scales beautifully for dashboards. Three completely different UIs coexisting in one app turned out to be much cleaner than I expected.


Try It Out

Β· πŸ”— Live Demo: maniestacampus.netlify.app
Β· πŸ’» Source Code: github.com/usmannmurtazaa/maniesta-campus-os

You can log in as different roles to see how the dashboards change. The RBAC behaviour is the most interesting part to explore.


What's Next

A few features I'm planning:

Β· Real-time notifications with Socket.io for grade updates and enrollment changes
Β· An analytics dashboard for admins (enrollment trends, popular courses)
Β· A mobile app built with React Native that shares the same API
Β· Bulk import from Excel so institutions can migrate their existing records


About the Author

I'm Usman Murtaza, a Full Stack Developer based in Karachi, Pakistan. I build modern web applications with React, Node.js, and MongoDB, and I'm the creator of the Maniesta ecosystem β€” a collection of web products and utilities.

Maniesta Campus is one of those products. Others include Maniesta Resume AI, Maniesta Suite, and the Maniesta brand platform itself.

· 🌐 Portfolio: usmanmurtaza.netlify.app
Β· πŸ’» GitHub: github.com/Usmannmurtazaa
Β· πŸ’Ό LinkedIn: linkedin.com/in/Usmannmurtazaa
· 🐦 Twitter/X: @usman_murtazaa

If you found this useful, follow me here on Dev.to β€” I write about full-stack development, React patterns, and building products from scratch.

Built by Usman Murtaza - see more projects at usmanmurtaza.netlify.app

Top comments (0)