π Live Demo: https://course-hub-frontend-phi.vercel.app/
- Overview
- Tech Stack
- Project Structure
- Backend API Documentation
- Database Schema
- Authentication
- Frontend Architecture
- Features
- Getting Started
- Environment Variables
CourseHub is a full-stack online learning platform that enables instructors to create and sell courses, and students to enroll and learn. The platform includes features like video uploads, payment processing, course management, and video compression capabilities. Course Hub Built earlier in my learning journey; later projects improve backend security and auth models.
- π User Authentication: Google OAuth integration
- π Course Management: Create, update, publish, and manage courses
- π₯ Video Upload & Processing: Support for video uploads with compression
- π³ Payment Integration: Stripe payment processing
- π Enrollment System: Students can enroll in courses
- ποΈ Video Compression: Built-in video compression to reduce file sizes
- βοΈ Cloud Storage: Cloudinary integration for media storage
Important
Note on Video Compression & Production Environment
This project implements a sophisticated FFmpeg pipeline designed to compress video uploads before they reach the cloud. This ensures optimal storage usage on Cloudinary and faster streaming for users.
- The Implementation: The backend includes logic to process video buffers through FFmpeg, reducing file sizes significantly while maintaining visual fidelity.
- Environment Constraints: While the code is fully functional and tested in local development, the Render Free Tier used for the live demo has a 512MB RAM limit. Heavy video encoding is a CPU/RAM-intensive process that exceeds these hardware limits, causing the free-tier server to restart.
- Engineering Insight: To maintain 100% uptime for the live demo, the heavy compression task is limited in production. However, the source code demonstrates a production-ready media pipeline that would scale seamlessly on a dedicated VPS or AWS EC2 instance.
- Runtime: Node.js
- Framework: Express.js
- Language: TypeScript
- Database: MongoDB (via Prisma ORM)
- Authentication: Google OAuth (NextAuth.js on frontend)
- File Storage: Cloudinary
- Payment: Stripe
- Video Processing: FFmpeg
- File Upload: Multer
- Framework: Next.js 15 with App Router
- Language: TypeScript
- Styling: Tailwind CSS
- UI Components: Radix UI + custom components
- State Management: Zustand
- Authentication: NextAuth.js
- HTTP Client: Axios
- ORM: Prisma
- Database: MongoDB
- Cloudinary: Image and video storage
- Stripe: Payment processing
- Google APIs: OAuth authentication
- FFmpeg: Video compression
CourseHubFinale/
βββ π¦ CourseHub_backend/ # Node.js/Express backend
β βββ src/
β β βββ controllers/ # Route controllers
β β βββ routes/ # API routes
β β βββ libs/ # Utility functions
β β βββ app.ts # Main server file
β βββ prisma/
β β βββ schema.prisma # Database schema
β βββ package.json
βββ π¨ CourseHub_Frontend/ # Next.js frontend
βββ app/ # Next.js app directory
β βββ api/ # API routes
β βββ courses/ # Course pages
β βββ dashboard/ # Dashboard pages
β βββ login/ # Authentication
βββ components/ # React components
βββ lib/ # Utilities
βββ package.json
http://localhost:8000
The backend doesn't handle authentication directly. Authentication is managed by the frontend using NextAuth.js, and user information is passed to the backend via request headers/body.
| Method | Endpoint | Description | Request Body | Response |
|---|---|---|---|---|
| POST | /register |
Register a new user | { name, email, imageUrl } |
User object |
| GET | / |
Get all users | - | Array of users |
| GET | /:id |
Get user by ID | - | User object with compressed videos |
| POST | /update |
Update user with profile picture | FormData: { id, name, file } |
Updated user |
| POST | /update/withoutfile |
Update user without picture | { id, name } |
Updated user |
| Method | Endpoint | Description | Request Body | Response |
|---|---|---|---|---|
| POST | /create |
Create a new course | FormData: { title, description, userId, price, file } |
Course object |
| GET | /get-all |
Get all published courses | - | Array of courses |
| GET | /:id |
Get course by ID | Query: user_id |
Course with videos and enrollment status |
| GET | /user/:userId |
Get courses by user ID | - | User's courses with videos |
| POST | /publish/:id |
Publish a course | { userId } |
Updated course |
| POST | /inactive/:id |
Make course inactive | { userId } |
Updated course |
| POST | /delete/:id |
Delete a course | { userId } |
Deleted course |
| POST | /update/:id |
Update course with image | FormData: { userId, title, description, price, file } |
Updated course |
| POST | /update-without-file/:id |
Update course without image | { userId, title, description, price } |
Updated course |
| Method | Endpoint | Description | Request Body | Response |
|---|---|---|---|---|
| POST | /image |
Upload image file | FormData: { file } |
{ public_id, secure_url } |
| POST | /video |
Upload video file | FormData: { title, courseId, userId, file } |
Video object with URL |
| POST | /video-compressing |
Upload and compress video | FormData: { userId, file } |
Compressed video object |
| Method | Endpoint | Description | Request Body | Response |
|---|---|---|---|---|
| GET | /:userId |
Get enrolled courses for user | - | Array of enrolled courses |
| POST | / |
Enroll in a course | { userId, courseId } |
Stripe checkout session |
| GET | /course/:courseId |
Get course details for enrolled user | - | Course with videos |
| GET | /:courseId/videos/:videoId |
Get specific video for enrolled user | - | Video object |
| Method | Endpoint | Description | Request Body | Response |
|---|---|---|---|---|
| POST | / |
Verify Stripe payment | Query: session_id |
{ success: boolean } |
// Request (FormData)
{
"title": "React Fundamentals",
"description": "Learn React from scratch",
"userId": "user123",
"price": 99,
"file": <image_file>
}
// Response
{
"id": "course123",
"title": "React Fundamentals",
"description": "Learn React from scratch",
"imageUrl": "https://cloudinary.com/image.jpg",
"userId": "user123",
"price": 99,
"status": "INACTIVE",
"publicId": "coursehub/images/abc123"
}// Request
{
"userId": "user123",
"courseId": "course456"
}
// Response
{
"sessionId": "cs_test_123",
"url": "https://checkout.stripe.com/pay/..."
}model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
imageUrl String
imagePublicId String?
email String @unique
role Role @default(STUDENT)
courses Course[]
enrollments Enrollment[]
compressedVideos CompressedVideo[]
accountBalance Int @default(0)
isPro Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}model Course {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
imageUrl String
publicId String
description String
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
videos Video[]
enrollments Enrollment[]
price Int
status CourseStatus @default(INACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}model Video {
id String @id @default(auto()) @map("_id") @db.ObjectId
courseId String @db.ObjectId
title String
course Course @relation(fields: [courseId], references: [id])
videoUrl String
videoDuration String
userId String @db.ObjectId
videoSize String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}model Enrollment {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String @db.ObjectId
courseId String @db.ObjectId
course Course @relation(fields: [courseId], references: [id])
user User @relation(fields: [userId], references: [id])
courseName String
courseImageUrl String
courseDescription String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}model CompressedVideo {
id String @id @default(auto()) @map("_id") @db.ObjectId
videoUrl String
videoDuration String
videoSize String
originalSize String
compressionRate String
date String
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
}enum Role {
STUDENT
INSTRUCTOR
ADMIN
}
enum CourseStatus {
PUBLISHED
INACTIVE
}-
Frontend Authentication:
- Uses NextAuth.js with Google Provider
- Google OAuth credentials are configured
- Session management handled by NextAuth.js
-
User Registration:
- When a user signs in with Google for the first time, the app automatically creates a user record in the database
- Subsequent logins update the session with user information from the database
-
Backend Communication:
- The backend expects user ID/email in request bodies for user-specific operations
- No JWT tokens or session management on the backend
- Backend trusts the frontend to provide valid user information
// Key features:
- Google OAuth provider
- Automatic user creation on first sign-in
- Session callback to enrich session with user data
- Database integration via Prismaapp/
βββ api/auth/[...nextauth]/ # NextAuth.js API route
βββ courses/ # Course-related pages
β βββ [id]/ # Individual course page
β βββ create/ # Create course page
βββ dashboard/ # User dashboard
βββ compress-video/ # Video compression tool
βββ compressed-videos/ # View compressed videos
βββ login/ # Login page
βββ payment/ # Payment success/cancel pages
βββ layout.tsx # Root layout
βββ page.tsx # Homepage
Navbar: Top navigation with authentication statusSidebar: Mobile-friendly navigation menuFooter: Site footerSessionWrapper: NextAuth session provider
HomeComponent: Landing page with course showcaseDashboard: User dashboard for managing courses/enrollmentsCourseCreate: Course creation formCourseView: Individual course viewing pageVideoCompression: Video compression utility
- Button variants with Tailwind CSS
- Modal dialogs
- Progress indicators
- Form controls
- Tabs and navigation elements
- Uses Zustand for client-side state management
- NextAuth for authentication state
- React hooks for local component state
- Tailwind CSS for utility-first styling
- Custom component library with
class-variance-authority - Responsive design with mobile-first approach
- Dark/light mode support (via Tailwind)
- π Browse published courses
- π° Enroll in courses via Stripe payment
- π₯ Access enrolled course content
- πΊ Watch course videos
- π View course progress
- β Create and manage courses
- β¬οΈ Upload course images and videos
- π€ Publish/unpublish courses
- βοΈ Edit course details
- π Track enrollments
- ποΈ Use video compression to reduce file sizes
- π Google OAuth authentication
- π€ Profile management
- π Dashboard for courses and enrollments
- ποΈ Video compression utility (saves to user's account)
- π§ Built-in FFmpeg compression
- π Reduces video file sizes by up to 90%
- π― Maintains reasonable video quality
- πΎ Saves compressed videos to user's account
- π Shows compression statistics (original size, compressed size, compression rate)
- Node.js (v18 or higher)
- MongoDB database
- Cloudinary account
- Stripe account
- Google OAuth credentials
- Clone the repository
git clone https://github.com/sarwanazhar/CourseHub
cd CourseHubFinale- Install backend dependencies
cd CourseHub_backend
npm install- Install frontend dependencies
cd ../CourseHub_Frontend
npm install
# or if using pnpm
pnpm install-
Set up environment variables (see Environment Variables section)
-
Set up database
cd ../CourseHub_backend
npx prisma generate
npx prisma db push- Start the backend server
npm run dev- Start the frontend development server (in a new terminal)
cd ../CourseHub_Frontend
npm run dev- Access the application
- Frontend: http://localhost:3000
- Backend: http://localhost:8000
# Database
DATABASE_URL="mongodb://localhost:27017/coursehub"
# Cloudinary
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# Stripe
STRIPE_SECRET_KEY=sk_test_...
# Frontend URL (for Stripe redirects)
FRONTEND_URL=http://localhost:3000# Backend API URL
NEXT_PUBLIC_API_URL=http://localhost:8000
# NextAuth
NEXTAUTH_SECRET=your_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
# Google OAuth
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
API_URL=your_backend_url
# Database (for Prisma)
DATABASE_URL="mongodb://localhost:27017/coursehub"curl -X POST http://localhost:8000/course/create \
-F "title=React Fundamentals" \
-F "description=Learn React from scratch" \
-F "userId=user123" \
-F "price=99" \
-F "file=@course-image.jpg"const response = await fetch('http://localhost:8000/enrolled', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: 'user123',
courseId: 'course456'
})
});
const { sessionId, url } = await response.json();
// Redirect user to Stripe checkout URL
window.location.href = url;const formData = new FormData();
formData.append('file', videoFile);
formData.append('title', 'Introduction to React');
formData.append('courseId', 'course123');
formData.append('userId', 'user123');
const response = await fetch('http://localhost:8000/upload/video', {
method: 'POST',
body: formData
});
const videoData = await response.json();The backend includes a Dockerfile for containerized deployment:
cd CourseHub_backend
docker build -t coursehub-backend .
docker run -p 8000:8000 coursehub-backendThe frontend is configured for Vercel deployment and is currently live at: https://course-hub-frontend-phi.vercel.app/
For manual deployment:
cd CourseHub_Frontend
npm run build
npm start- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
For support or questions, please open an issue on the GitHub repository.
π‘ Note: This is a comprehensive documentation of the CourseHub project. The live demo is available at https://course-hub-frontend-phi.vercel.app/ for testing the application's features.
π¨βπ» Developed and Managed by Sarwan Azhar
π GitHub Repository: https://github.com/sarwanazhar/CourseHub