Learnly Hero

Learnly

Compliance Training Tracker — Frontend, Backend, Role-based access.

Case Study

Learnly — Compliance Training Tracker

A role-based training management platform ensuring employees complete mandatory compliance sessions. Built with React, Node.js/Express, and MongoDB Atlas. Backend-first approach: structured data, access control, and durable audit-ready storage.

Overview

Learnly streamlines compliance training management for organizations. Four user roles (Admin, Manager, Trainer, Employee) ensure secure role-based workflows: admins configure training, managers assign sessions, trainers record completion, and employees enroll/track progress.

Challenge

Compliance training data is often fragmented across spreadsheets, emails, and ad-hoc tools. This creates risk during audits and slows organizational oversight.

Outcome

A centralized, MongoDB-backed app that enforces consistency: training programs, assignments, and completions are all stored in a single source of truth, accessible by role and secured with authentication.

1 Database
MongoDB Atlas as the single source of truth
Audit-Ready
Complete records for compliance checks
Role-Based Access
Four roles with tailored permissions

Role & Timeline

Role
Project Manager, Database Designer, Full-Stack Developer (ERD, backend architecture, Atlas integration, deployment setup)
Timeline
~10 weeks (design sprints, backend-first build, frontend integration)
Tools
Figma, React, Node.js, Express, MongoDB Atlas, GitHub, Netlify, Vercel

Personas

Maria — HR Manager

Bio: Oversees compliance for 120 employees.

Goals: Assign sessions, track completions, avoid audit fines.

Pain: Juggling spreadsheets and email reminders.

Fit: Uses Learnly’s dashboard to assign training and monitor completion rates.

James — Employee

Bio: Customer service rep with mandatory yearly training.

Goals: Easily find and complete required sessions.

Pain: Forgetting due dates or losing track of PDFs.

Fit: Accesses Learnly via mobile to view assigned sessions and mark completion.

Style Guide

Professional and minimal UI with clear contrasts for role-based dashboards. Accessibility and readability prioritized.

Type: Inter for UI and body text, bold weights for headings.

Process

1. Planning

  • ERD designed in Lucidchart with collections for Users, Trainings, Sessions, Assignments.
  • Stack selected: MERN (React, Node/Express, MongoDB Atlas).
  • Deployment plan: Netlify (frontend) + Vercel (backend).
Application Sitemap

High-level navigation for each role and shared routes.

Learnly Application Sitemap
Site map showing role-based sections and public routes.

2. Backend Build

  • Express server with modular routes (users, sessions, assignments).
  • Mongoose models enforcing schema validation.
  • Role-based access control middleware.
Backend Wireframe
Learnly Backend Wireframe
Service boundaries, routes, controllers, and middleware.

3. Database Design

  • Express server with modular routes (users, sessions, assignments).
  • Mongoose models enforcing schema validation.
  • Role-based access control middleware.
Database ERD
Learnly Database ERD
Users/Roles, TrainingPrograms, TrainingSessions, Assignments, Enrollments, CompletionStatus (MongoDB schemas via Mongoose).

4. Frontend Integration

  • React UI with dashboards per role.
  • Forms for session creation, assignment, and completion logging.
Wireframes - High-Fidelity Prototypes
Mobile wireframes: Role dashboards.
Learnly Mobile Wireframe 1
Learnly Mobile Wireframe 2
Learnly Mobile Wireframe 3
Learnly Mobile Wireframe 4
Desktop wireframes: Trainer session flows. User tables, and view and create new trainings pages.
Learnly Desktop Wireframe 1
Learnly Desktop Wireframe 2
Learnly Desktop Wireframe 3

Full wireframes in Figma: open file ↗

5. Deployment & Testing

  • Frontend deployed on Netlify.
  • Backend deployed on Vercel (serverless functions).
  • Team presentations + YouTube demo video.

Final Design

  • Role Dashboards: Admin, Manager, Trainer, Employee views.
  • Session Management: Create and assign compliance trainings.
  • Completion Tracking: Trainers and employees log completion.
  • Responsive: Mobile-friendly for on-the-go access.

Engineering

Stack & Project Layout

client/              # React (role dashboards, forms, tables)
server/
  src/
    models/         # Mongoose schemas (User, Role, Training, Session, Assignment, Enrollment, Completion)
    middleware/     # auth.js (JWT), authorizeRole.js (RBAC), errorHandler.js
    routes/         # /auth, /users, /trainings, /sessions, /assignments, /enrollments, /completions
    controllers/    # handler logic per resource
    db.js           # MongoDB Atlas connection
    app.js          # Express app (CORS, JSON, routes, errors)
  serverless.json   # Vercel config (if using)

Database Requirements & Schemas (Mongoose)

// models/Role.js
const RoleSchema = new mongoose.Schema({
  name: { type: String, enum: ["ADMIN","MANAGER","TRAINER","EMPLOYEE"], required: true }
});

// models/User.js
const UserSchema = new mongoose.Schema({
  firstName: String,
  lastName: String,
  email: { type: String, unique: true, index: true },
  passwordHash: String,
  role: { type: mongoose.Schema.Types.ObjectId, ref: "Role", required: true }
});

// models/TrainingProgram.js
const TrainingProgramSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: String,
  duration: Number, // minutes or hours
  manager: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
  deadline: Date
});

// models/TrainingSession.js
const TrainingSessionSchema = new mongoose.Schema({
  training: { type: mongoose.Schema.Types.ObjectId, ref: "TrainingProgram", required: true },
  trainer: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
  sessionDate: Date,
  sessionTime: String
});

// models/Assignment.js
const AssignmentSchema = new mongoose.Schema({
  training: { type: mongoose.Schema.Types.ObjectId, ref: "TrainingProgram", required: true },
  employee: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
  assignedByManager: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});

// models/Enrollment.js
const EnrollmentSchema = new mongoose.Schema({
  employee: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
  session: { type: mongoose.Schema.Types.ObjectId, ref: "TrainingSession", required: true },
  enrollmentDate: { type: Date, default: Date.now }
});

// models/CompletionStatus.js
const CompletionStatusSchema = new mongoose.Schema({
  enrollment: { type: mongoose.Schema.Types.ObjectId, ref: "Enrollment", required: true },
  trainer: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
  completionDate: Date,
  status: { type: String, enum: ["COMPLETED","IN_PROGRESS","FAILED"], default: "COMPLETED" }
});

Connecting to MongoDB Atlas

// db.js
import mongoose from "mongoose";
const uri = process.env.MONGODB_URI; // mongodb+srv://user:pw@cluster/db?retryWrites=true&w=majority

export async function connectDB() {
  mongoose.set("strictQuery", true);
  await mongoose.connect(uri, { dbName: process.env.MONGODB_DB || "learnly" });
  console.log("✅ Connected to MongoDB Atlas");
}

.env (backend): MONGODB_URI, JWT_SECRET, CORS_ORIGIN, etc.

Auth & Role-Based Dashboards

// middleware/auth.js (JWT)
export function requireAuth(req,res,next){
  const token = req.headers.authorization?.replace("Bearer ","");
  if(!token) return res.status(401).send("Unauthorized");
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch { return res.status(401).send("Invalid token"); }
}

// middleware/authorizeRole.js
export const authorizeRole = (...allowed) => (req,res,next) => {
  if(!allowed.includes(req.user.role)) return res.status(403).send("Forbidden");
  next();
};

REST Endpoints (examples)

// routes/trainings.js
router.post("/", requireAuth, authorizeRole("ADMIN","MANAGER"), createTraining);
router.get("/", requireAuth, listTrainings);

// routes/assignments.js
router.post("/", requireAuth, authorizeRole("MANAGER"), assignTrainingToEmployee);
router.get("/me", requireAuth, listAssignmentsForCurrentUser);

// routes/completions.js
router.post("/", requireAuth, authorizeRole("TRAINER"), markCompletion);
router.get("/", requireAuth, authorizeRole("ADMIN","MANAGER"), listCompletionsByProgram);

Frontend: Showing Different Dashboards

// client/src/App.jsx (conceptual)
const { user } = useAuth();
if (!user) return <AuthScreens />;

switch (user.role) {
  case "ADMIN":   return <AdminDashboard />;
  case "MANAGER": return <ManagerDashboard />;
  case "TRAINER": return <TrainerDashboard />;
  default:        return <EmployeeDashboard />;
}

Each dashboard calls role-appropriate endpoints and renders tables (trainings, sessions, assignments, enrollments, completions) with filters, due dates, and actions.

Data Integrity & Auditing

  • Refs between documents (e.g., Completion → Enrollment → Session/Training) preserve traceability.
  • Server validation ensures only trainers can mark completions; managers assign; admins configure.
  • Timestamps on all writes provide audit trails.

Key Learnings

  • Designing roles first clarifies schema relationships and API boundaries.
  • Serverless + Atlas is fast to deploy, but requires careful cold-start and connection handling.
  • Consistent naming between UI, API, and DB reduces integration friction.

Impact

Learnly offered a scalable way for organizations to manage compliance training digitally. Instead of fragmented tools, all data and workflows became centralized, reducing audit risk and saving managers time.

© Learnly Case Study — UX + Backend. Built with React, Node.js, Express, and MongoDB Atlas.