A full-featured Learning Management System where instructors create and publish video-based courses, and students browse, purchase (via Razorpay), and track their learning progress. Built with Next.js 14 App Router, Prisma ORM, MySQL, Mux for video streaming, and NextAuth.js for authentication.
How the system is structured from frontend to external services.
Server Components for static pages, Client Components for interactive features like drag-and-drop chapter reordering, Mux video playback, and the Razorpay payment modal. Zustand for global state management, SWR for data fetching and caching.
RESTful API routes handling course CRUD, purchase verification, and progress tracking. JWT-based sessions with credentials and Google OAuth providers. Server-side authorization checks on every protected route.
MySQL via Prisma for relational data — users, courses, chapters, purchases, and progress. Full-text search index on course titles. MongoDB with TTL indexes for time-sensitive OTP documents that auto-expire after 45 seconds.
Mux handles video encoding, adaptive streaming, and CDN delivery. Razorpay processes payments with HMAC-SHA256 signature verification. UploadThing manages file uploads for images, videos, and attachments. Nodemailer sends OTP emails via Gmail SMTP.
Core entities and how they relate to each other.
Central user account — supports both credentials (email/password) and Google OAuth sign-in
id (uuid)nameemail (unique)password (hashed, optional for OAuth)phoneNumber (unique)emailVerifiedimageOAuth provider links — stores Google OAuth tokens and provider metadata for each connected account
providerproviderAccountIdaccess_tokenrefresh_tokenexpires_at@@unique([provider, providerAccountId])Stores active user sessions with unique tokens and expiry timestamps
sessionToken (unique)expiresuserIdCourse metadata with publish/unpublish workflow — requires title, description, image, category, and at least one published chapter to go live
userIdtitle (full-text indexed)descriptionimageUrlpriceisPublishedcategoryIdCourse categories for catalog filtering — seeded with values like Web Development, Data Science, etc.
id (uuid)name (unique)Individual lessons within a course — supports ordering, free preview access, and Mux video hosting
titledescriptionvideoUrlposition (int, drag-and-drop order)isPublishedisFreeLinks a chapter to its Mux video asset — deleted and recreated when the instructor replaces a video
assetIdplaybackIdchapterId (unique)Downloadable course resources (PDFs, documents) that students can access after purchase
nameurlcourseIdRecords successful Razorpay transactions — stores order and payment IDs for audit trail
userIdcourseIdorderId (unique)paymentId (unique)@@unique([userId, courseId])Per-chapter completion tracking — aggregated to calculate course progress percentage
userIdchapterIdisCompleted@@unique([userId, chapterId])Email verification tokens with identifier and expiry — managed by NextAuth.js Prisma Adapter for email verification flows
id (uuid)identifiertoken (unique)expires@@unique([identifier, token])Rate-limits password reset attempts — tracks how many OTPs a user has requested to prevent abuse
email (unique)countlastReset (BigInt timestamp)Stores one-time passwords for password reset — Prisma model for relational linking, Mongoose model with 45-second TTL auto-expiry for the actual verification
email (unique)otp (6-digit code)otpTypeTTL index: expireAfterSeconds: 45Step-by-step walkthrough of the main user and system flows.
The purchase API could be called directly with fake payment responses, allowing unauthorized course access without actual payment.
Server-side HMAC-SHA256 verification of Razorpay's signature by concatenating order_id|payment_id and comparing against the hash. Added a Prisma @@unique constraint on [userId, courseId] to prevent duplicate purchases at the database level.
The /send-otp endpoint could be abused for email flooding, and attackers could brute-force the 6-digit code with repeated guesses.
Rate limiting via MySQL PasswordResetRequest model tracks request count and timestamps. MongoDB TTL index auto-expires OTP documents after 45 seconds, making brute force impractical within the time window.
When instructors replace or delete chapter videos, the old Mux assets pile up on the account and keep incurring costs.
Before every PATCH/DELETE on a chapter, check for existing MuxData and delete the old Mux asset first. Cascade deletion on course removal ensures no orphaned video assets remain.
Publishing a course with missing chapters, images, or descriptions leads to broken product listings visible to students.
Validation gate requires title, description, image, category, and at least one published chapter before allowing publish. Auto-unpublish triggers when the last published chapter is deleted or unpublished.
CodeCrafters demonstrates full-stack development with Next.js 14, secure payment processing via Razorpay, video streaming infrastructure with Mux, and complex database relationships using Prisma. The project showcases handling real-world edge cases like OTP rate limiting, video asset lifecycle management, and cascading publish/unpublish logic.