A modern, feature-rich Learning Management System built with the MERN stack, featuring video streaming, payment processing, and real-time progress tracking.
Mindure is a comprehensive LMS platform that enables educators to create and monetize courses while providing students with an engaging learning experience. The platform supports both YouTube-embedded and Cloudinary-hosted video content, implements secure payment processing, and tracks student progress in real-time.
Live Demo: https://mindure.vercel.app
- โจ Key Features
- ๐๏ธ System Architecture
- ๐ฏ Technical Highlights
- ๐ Project Structure
- ๐ Getting Started
- ๐ What I Learned
- ๐ Key Takeaways
- ๐ฎ Future Enhancements
- ๐ค Contributing
- ๐ License
- ๐จโ๐ป Author
- ๐ Acknowledgments
- ๐ง Contact
- ๐ Secure Authentication - Powered by Clerk for seamless signup/login
- ๐ Course Browsing - Explore courses with advanced filtering
- ๐ฅ Adaptive Video Streaming - Smooth playback with buffering indicators
- ๐ Progress Tracking - Track completion status for each lecture
- โญ Course Ratings - Rate and review completed courses
- ๐ณ Secure Payments - Stripe integration for safe transactions
- ๐ฑ Responsive Design - Works seamlessly on all devices
- ๐ Course Creation - Intuitive course builder with rich text editor
- ๐ฌ Flexible Video Uploads - Support for YouTube URLs and direct video uploads
- ๐ฐ Revenue Dashboard - Track earnings and student enrollments
- ๐ฅ Student Analytics - Monitor student engagement and progress
- ๐ท๏ธ Dynamic Pricing - Set prices and discounts for courses
- ๐ Performance Metrics - Real-time analytics on course performance
- ๐ ๏ธ User Management - Role-based access control (Student/Educator)
- ๐ Platform Analytics - Overview of platform-wide metrics
- ๐ Webhook Integration - Real-time updates via Clerk and Stripe webhooks
Frontend:
- โ๏ธ React 19.1.1 - Latest React with improved performance
- ๐จ Tailwind CSS 3.4.17 - Utility-first styling with PostCSS
- ๐ฃ๏ธ React Router DOM 7.9.1 - Client-side routing
- ๐ก Axios 1.12.2 - HTTP client for API calls
- ๐ React Toastify 11.0.5 - Toast notifications
- ๐ฌ React YouTube 10.1.0 - YouTube video embedding
- ๐ Quill 2.0.3 - Rich text editor for course descriptions
- โญ React Simple Star Rating 5.1.7 - Course rating component
- ๐ RC Progress 4.0.0 - Progress bars for course completion
- ๐ญ Framer Motion 12.23.24 - Smooth animations
- โฐ Humanize Duration 3.33.1 - Format video durations
- ๐ช Reactjs Popup 2.0.6 - Modal components
- ๐ Clerk React 5.47.0 - Authentication SDK
- โก Vite 7.1.2 - Fast build tool and dev server
Backend:
- ๐ข Node.js & Express 5.1.0 - RESTful API server
- ๐ MongoDB & Mongoose 8.18.3 - NoSQL database & ODM
- ๐ @clerk/express 1.7.35 - Authentication middleware
- ๐ณ Stripe 19.0.0 - Payment processing
- โ๏ธ Cloudinary 2.7.0 - Video/image hosting & optimization
- ๐ Svix 1.42.0 - Webhook verification
- ๐ฆ Multer 2.0.2 - File upload handling
- โ Validator 13.15.15 - Input validation
- ๐ CORS 2.8.5 - Cross-origin resource sharing
- ๐ง Nodemon 3.1.10 - Development auto-restart
DevOps & Tools:
- ๐ Vercel - Serverless deployment (frontend & backend)
- ๐ Git & GitHub - Version control
- ๐จ ESLint 9.33.0 - Code linting
- ๐ฆ PostCSS 8.5.6 - CSS processing
- ๐ dotenv 17.2.3 - Environment variable management
The Challenge: Initially, videos were laggy when seeking (clicking on the timeline). Users experienced long buffering times and poor playback quality.
The Solution: Implemented Cloudinary's adaptive streaming with optimized transformations:
// Cloudinary upload with streaming profile
formData.append("eager", "sp_hd/q_auto:good,f_auto");
formData.append("eager_async", "true");
// URL transformation for existing videos
const optimizedUrl = `${baseUrl}/sp_hd/q_auto,f_auto/${videoPath}`;Impact:
- โ 70% faster seeking times
- โ 50% reduction in bandwidth usage
- โ Smooth HD streaming on all devices
YouTube Integration:
// YouTube URL validation and embedding
const isValidYouTubeUrl = (url) => {
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/;
return pattern.test(url);
};Cloudinary Direct Upload:
// Chunked upload with progress tracking
const uploadToCloudinary = async (file) => {
const formData = new FormData();
formData.append("file", file);
formData.append("chunk_size", "6000000"); // 6MB chunks
return axios.post(cloudinaryUrl, formData, {
onUploadProgress: (e) => {
const progress = Math.round((e.loaded * 100) / e.total);
setUploadProgress(progress);
},
});
};Benefits:
- ๐ฅ Flexibility for educators (YouTube or upload)
- ๐ฆ No storage limits with YouTube option
- ๐ฏ Full control with Cloudinary uploads
- ๐ฐ Cost optimization based on needs
Why Clerk: Traditional auth (JWT, sessions, bcrypt) is complex and error-prone. Clerk provides:
- ๐ Production-ready security out of the box
- ๐จ Beautiful, customizable UI components
- ๐ Automatic token refresh
- ๐ง Email verification & password reset
- ๐ค User profile management
- ๐ญ Role-based access control
Implementation:
// Clerk webhook for user sync
export const clerkWebhook = async (req, res) => {
const { type, data } = req.body;
if (type === "user.created") {
await User.create({
clerkId: data.id,
email: data.email_addresses[0].email_address,
name: `${data.first_name} ${data.last_name}`,
role: data.public_metadata.role || "student",
});
}
};Developer Experience:
- โฑ๏ธ Reduced auth development time from 2 weeks to 2 days
- ๐ Zero auth-related bugs in production
- ๐ Easy integration with protected routes
- ๐ฑ Mobile-ready without extra work
Route Protection:
// Role-based route protection
const ProtectedRoute = ({ children, allowedRoles }) => {
const { userData } = useAuth();
if (!userData) return <Navigate to="/login" />;
if (!allowedRoles.includes(userData.role)) {
return <Navigate to="/unauthorized" />;
}
return children;
};Webhook-Driven Flow:
// Stripe webhook handler
// CASE 1: Payment successful and checkout completed
case "payment_intent.succeeded": {
try {
const paymentIntent = event.data.object;
const { purchaseId, userId, courseId } = paymentIntent.metadata;
// Validate metadata exists
if (!purchaseId || !userId || !courseId) {
console.error(
"Missing metadata in payment intent:",
paymentIntent.id
);
return res.status(400).json({ error: "Missing metadata" });
}
// Fetch all relevant data from database
const purchaseData = await Purchase.findById(purchaseId);
const userData = await User.findById(userId);
const courseData = await Course.findById(courseId);
// Prevents crashes if metadata contains invalid IDs
if (!purchaseData || !userData || !courseData) {
console.error("Missing data:", { purchaseId, userId, courseId });
return res.status(400).json({ error: "Invalid metadata" });
}
// This ensures a student isn't enrolled multiple times
if (!courseData.enrolledStudents.includes(userId)) {
courseData.enrolledStudents.push(userData._id);
await courseData.save();
}
// Check if course already in user's enrollments
if (!userData.enrolledCourses.includes(courseId)) {
userData.enrolledCourses.push(courseData._id);
await userData.save();
}
purchaseData.status = "completed";
await purchaseData.save();
} catch (error) {
// If any database operation fails, we catch and log it
console.error("Checkout session error:", error);
return res.status(500).json({ error: "Internal server error" });
}
break;
}Security Features:
- โ Webhook signature verification
- โ Idempotent operations (prevent duplicates)
- โ Metadata validation
- โ Error handling and logging
Reusable Component:
<EmptyState
imageSrc="graduation"
title="No Enrollments Yet"
description="Browse our catalog and start learning!"
actionLabel="Browse Courses"
onAction={() => navigate("/courses")}
/>Impact on UX:
- ๐จ Consistent design across the platform
- ๐ฆ Clear call-to-actions for users
- ๐ Friendly, encouraging messaging
- ๐ฑ Device-agnostic SVG icons (no emoji issues)
MERN_LMS/
โโโ client/ # Frontend React application
โ โโโ src/
โ โ โโโ components/
โ โ โ โโโ common/ # Reusable components (EmptyState, VideoPlayer)
โ โ โ โโโ students/ # Student-specific UI (Footer, Loading)
โ โ โ โโโ educator/ # Educator-specific UI (CourseForm, Analytics)
โ โ โโโ pages/
โ โ โ โโโ student/ # Student pages (MyEnrollments, Player)
โ โ โ โโโ educator/ # Educator pages (Dashboard, MyCourses)
โ โ โ โโโ common/ # Shared pages (Home, CourseDetails)
โ โ โโโ context/ # React Context providers
โ โ โ โโโ AppContext.jsx # Global app state
โ โ โ โโโ AuthContext.jsx # Authentication state
โ โ โ โโโ EnrollmentContext.jsx # Enrollment management
โ โ โโโ hooks/ # Custom React hooks
โ โ โ โโโ useCourseData.js
โ โ โโโ utils/ # Helper functions
โ โ โ โโโ courseHelpers.js
โ โ โโโ assets/ # Static assets (images, icons)
โ โ โโโ App.jsx # Main app component
โ โ โโโ main.jsx # Entry point
โ โโโ public/
โ โโโ index.html
โ โโโ vite.config.js
โ โโโ tailwind.config.js
โ โโโ package.json
โโโ server/ # Backend Node.js application
โ โโโ models/ # MongoDB schemas
โ โ โโโ User.js
โ โ โโโ Course.js
โ โ โโโ Purchase.js
โ โ โโโ Progress.js
โ โโโ routes/ # API routes
โ โ โโโ courseRoutes.js
โ โ โโโ userRoutes.js
โ โ โโโ educatorRoutes.js
โ โโโ controllers/ # Route handlers
โ โ โโโ webhooks.js # Clerk & Stripe webhooks
โ โ โโโ courseController.js
โ โโโ middleware/ # Custom middleware
โ โ โโโ protectEducator.js
โ โโโ configs/ # Configuration files
โ โ โโโ mongodb.js
โ โ โโโ cloudinary.js
โ โโโ server.js # Entry point
โ โโโ .env # Environment variables
โ โโโ package.json
โโโ README.md
- Node.js 18+
- MongoDB Atlas account
- Cloudinary account
- Stripe account
- Clerk account
1. Clone the repository:
git clone https://github.com/amzilox/MERN_LMS.git
cd MERN_LMS2. Install dependencies:
# Frontend
cd client
npm install
# Backend
cd ../server
npm install3. Environment Variables:
Make sure to create separate .env files inside both the client and server folders.
Frontend (client/.env):
VITE_BACKEND_URL=http://localhost:5000
VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
VITE_CLOUDINARY_UPLOAD_PRESET=your_preset
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxBackend (server/.env):
PORT=5000
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/lms
CLERK_WEBHOOK_SECRET=whsec_xxxxx
CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
CLERK_SECRET_KEY=sk_test_xxxxx
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret4. Run the application:
# Backend (terminal 1)
cd server
npm run server
# Frontend (terminal 2)
cd client
npm run devThe app requires both servers to run concurrently. Make sure MongoDB Atlas and all environment variables are properly configured before testing.
5. Access the application:
- Frontend: http://localhost:5173 (Vite default)
- Backend: http://localhost:5000
- โ Advanced state management with Context API
- โ Custom hooks for reusable logic
- โ Component composition patterns
- โ Performance optimization techniques
- โ Error boundary implementation
- โ RESTful API design principles
- โ Authentication vs Authorization
- โ Webhook handling and security
- โ File upload strategies
- โ Real-time data synchronization
- โ Environment-specific configurations
- โ CORS and security headers
- โ Vercel deployment optimization
- โ Domain management
- โ Production debugging
- โ Clean code principles
- โ Component reusability
- โ Error handling patterns
- โ Loading states and UX feedback
- โ Responsive design
- โ Accessibility considerations
-
Video streaming is complex - Don't underestimate proper video optimization. Cloudinary's transformations saved the project.
-
Authentication is critical - Using Clerk instead of rolling my own saved weeks of development and eliminated security risks.
-
User feedback matters - Loading indicators, empty states, and error messages significantly improve UX.
-
Planning prevents problems - Taking time to architect the system properly made development much smoother.
-
Progressive enhancement - Starting with MVP and adding features iteratively kept scope manageable.
- Real-time chat between students and educators
- Live streaming classes
- Quiz and assignment system
- Certificate generation
- Mobile app (React Native)
- AI-powered course recommendations
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Mohamed Hamza AMZIL
- LinkedIn: Med Hamza AMZIL
- GitHub: @amzilox
- Clerk - Authentication platform
- Cloudinary - Video hosting
- Stripe - Payment processing
- Vercel - Deployment platform
- All open-source contributors
For questions or feedback, reach out at: amzilhamza45@gmail.com
โญ If you find this project helpful or inspiring, please consider giving it a star to support my work!



