A full-stack MERN ed-tech platform where instructors build and sell video courses and students buy, watch and review them.
Students
- Sign up with email OTP verification, log in, reset a forgotten password by email link
- Browse a catalog by category (with a "top selling" list and courses from another category), view course details and reviews
- Add courses to a cart and check out with Razorpay, or use the "Buy for free" demo path
- Watch lectures in a course player with a section/lecture sidebar, mark lectures complete and see progress percentages on the enrolled-courses page
- Rate and review courses they are enrolled in
Instructors
- Multi-step course builder: course info (thumbnail, price, category, tags, instructions), sections and lecture videos, then publish or keep as draft
- Edit course content (sections and lectures), publish or unpublish, and delete courses
- Dashboard with per-course enrolled students and income, plus a Chart.js pie chart
Everyone
- Profile, display picture and password settings; account deletion
- Contact form that sends a confirmation email
- Transactional emails: OTP, enrollment, payment received, password updated
flowchart LR
C["React SPA<br/>Redux Toolkit"] -->|"axios + Bearer JWT"| API["Express API"]
API --> MW["auth middleware<br/>JWT verify + role check"]
MW --> CT["Controllers"]
CT --> DB[("MongoDB<br/>Mongoose")]
CT -->|"thumbnails, videos, avatars"| CL["Cloudinary"]
CT -->|"OTP, receipts, resets"| ML["Nodemailer"]
CT -->|"create order"| RZ["Razorpay"]
C -->|"checkout.js"| RZ
Authentication. Signup is gated by a 6-digit OTP. The OTP model has a Mongoose pre("save") hook that emails the code, and a TTL index (5 minutes) that expires it. Passwords are hashed with bcrypt. Login issues a JWT (email, id, accountType, 2 h expiry), returned in the response and set as an httpOnly cookie. The auth middleware accepts the token from the cookie, the body or an Authorization: Bearer header. Password reset uses a random crypto token stored on the user with a 5-minute expiry.
Role-based access control. Users are Student, Instructor or Admin. Routes chain auth with isStudent, isInstructor or isAdmin, which look the user up in MongoDB and check accountType. For example, only instructors can create courses and sections, only students can buy courses or post reviews, and only admins can create categories. Reviews are also checked against the course's enrolled students.
Payments. capturePayment totals the cart server-side from course prices (it does not trust a client amount), rejects courses the student already owns, and creates a Razorpay order in INR. After checkout, verifyPayment recomputes HMAC-SHA256(order_id + "|" + payment_id) with the Razorpay secret and only enrolls the student (course studentsEnrolled, user courses, a new CourseProgress record, enrollment email) if it matches the returned signature.
Media. express-fileupload writes uploads to temp files, and uploadImage sends them to Cloudinary (resource_type: "auto", so it handles both images and lecture videos) under FOLDER_NAME.
Data model. User, Profile, OTP, Category, Course, Section, SubSection (lecture video), CourseProgress, RatingAndReview, Tag.
| Layer | Tools |
|---|---|
| Frontend | React 18 (Create React App), Redux Toolkit, React Router 6, Tailwind CSS 3, react-hook-form, Swiper, Chart.js, video-react, react-hot-toast |
| Backend | Node.js, Express 4, Mongoose 7, JSON Web Tokens, bcrypt, otp-generator, cookie-parser, express-fileupload |
| Services | MongoDB, Cloudinary, Razorpay, Nodemailer |
BrainWave/
├── src/ React app
│ ├── pages/ Home, Catalog, CourseDetails, Dashboard, auth pages
│ ├── components/ Dashboard (AddCourse builder, Cart, InstructorDashboard), ViewCourse player, Commons
│ ├── services/ APIs.js (endpoint map), APIconnector.js, operations/ (API calls per feature)
│ └── reducer/ Redux slices (auth, profile, cart, course, courseDetails)
└── BackEnd/
├── index.js Express app and route mounting
├── routes/ User (auth), Profile, Course, Payment, Contact
├── controllers/ business logic per resource
├── middlewares/ auth.js (JWT + role guards)
├── models/ Mongoose schemas
├── config/ database, cloudinary, razorpay
├── mailTemplates/ HTML email templates
└── utilities/ ImageUploader (Cloudinary), MailSender, convertTimeFormat
Prerequisites: Node.js, a MongoDB connection string, and Cloudinary, Razorpay (test mode) and SMTP credentials.
npm install # frontend
cd BackEnd && npm install # backend
cd .. && npm run dev # runs the React app (port 3000) and the API (nodemon) togetherOr run them separately with npm start (root) and npm run dev (in BackEnd/).
Create a .env file in both the project root and the BackEnd directory (see the .env.example files).
BackEnd/.env:
MONGO_URL=
PORT=4000
MAIL_HOST= # Nodemailer "service" name, e.g. gmail
MAIL_USER=
MAIL_PASS=
JWT_SECRET=
FOLDER_NAME= # Cloudinary folder for uploads
CLOUD_NAME=
API_KEY= # Cloudinary
API_SECRET= # Cloudinary
RAZORPAY_KEY=
RAZORPAY_SECRET=Root .env:
REACT_APP_BASE_URL=http://localhost:4000The API mounts its routers at the root (/auth, /course, ...), so the base URL should not include the /api/v1 suffix shown in BackEnd/.env.example.
A = requires a JWT. Role guards are noted where they apply.
| Router | Endpoints |
|---|---|
/auth |
POST /sendOTP, POST /signup, POST /login, POST /changePassword (A), POST /resetPasswordToken, POST /resetPassword |
/profile |
GET /getAllDetails (A), PUT /updateProfile (A), PUT /updateDisplayPicture (A), DELETE /deleteProfile (A), GET /getEnrolledCourses (A, Student), GET /instructorDashboard (A, Instructor) |
/course (instructor) |
POST /createCourse, POST /addSection, PUT /updateSection, DELETE /deleteSection, POST /addSubSection, PUT /updateSubSection, DELETE /deleteSubSection, PUT /changeStatus, GET /getInstructorCourses, DELETE /deleteCourse (all A, Instructor) |
/course (student) |
POST /getFullCourseDetails, POST /updateCourseProgress, POST /createRatingAndReview, POST /getStudentCourseRatingsAndReview (all A, Student) |
/course (public) |
GET /getAllCourses, POST /getCourseDetails, GET /showAllCategory, POST /getCategoryPageDetails, POST /getAverageRating, GET /getReviews |
/course (admin) |
POST /createCategory (A, Admin) |
/payment |
POST /capturePayment, POST /verifyPayment, POST /sendPaymentSuccessEmail, POST /buyCoursesForFree (all A, Student) |
/contact |
POST /contactUs |
Known issues found while reviewing the code, roughly in priority order:
- Payment verification:
BackEnd/controllers/Payment.jsusescryptowithout requiring it, so/payment/verifyPaymentthrows. The frontend also readsprocess.env.RAZORPAY_KEY, which Create React App does not expose (it needs aREACT_APP_prefix). - Security hardening:
/payment/buyCoursesForFreeenrolls a student without checking the course pricesendOTPreturns the OTP in its responsesignupaccepts anyaccountTypefrom the client, includingAdmin- course update/delete routes check the Instructor role but not course ownership
- Frontend/backend drift: the endpoint map calls
/profile/getUserDetailsand/course/editCourse, which do not exist on the server (the edit route is commented out), and the payment-success-email URL is missing a/. - Smaller bugs:
- the OTP
createdAtdefault isDate.now()(evaluated once at startup) instead ofDate.now - a few typos (
.josn,res.stauts) in error paths sendPaymentSuccessEmailnever sends a success response- the reset-password link is hardcoded to
http://localhost:3000
- the OTP
- Engineering: restrict CORS (currently
*), add request validation, centralize error handling, and add tests (there are none yet).