Skip to content

Moduo brings together real-time video calls, collaborative coding, and chat - a complete, modular platform for engineers to practice, conduct, or experience technical interviews like never before.

License

Notifications You must be signed in to change notification settings

KeepSerene/moduo-video-calling-interview-platform-mern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

54 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Moduo

Code together, live. A real-time collaborative coding interview platform built with the MERN stack.

Live Demo License

πŸ“‹ Table of Contents


🎯 Overview

Moduo is a comprehensive platform designed for conducting technical coding interviews in real-time. It combines video calling, live chat, and collaborative code editing into a single, seamless experience. Perfect for practicing DSA problems, conducting mock interviews, or hosting actual technical screenings.


✨ Features

  • πŸŽ₯ HD Video Calls - Crystal-clear video and audio powered by GetStream.io
  • πŸ’¬ Real-time Chat - Integrated messaging during interview sessions
  • πŸ‘¨β€πŸ’» Live Code Editor - Monaco editor with syntax highlighting for JavaScript, Python, and Java
  • ▢️ Code Execution - Run code directly in the browser using Piston API
  • πŸ” Secure Authentication - User management via Clerk
  • πŸ“Š Session Management - Create, join, and manage interview sessions
  • 🎨 Responsive Design - Beautiful UI with TailwindCSS and DaisyUI
  • πŸ“± Desktop-First - Optimized for desktop experience
  • πŸ”„ Resizable Panels - Flexible layout with adjustable sections

πŸ›  Tech Stack

Frontend

  • React 19 - UI library
  • Vite - Build tool and dev server
  • React Router 7 - Client-side routing
  • TailwindCSS 4 + DaisyUI - Styling framework
  • Monaco Editor - Code editor component
  • GetStream.io Video & Chat SDKs - Real-time communication
  • Clerk React - Authentication
  • TanStack Query - Server state management
  • React Hot Toast - Notifications
  • Lucide React - Icons
  • Canvas Confetti - Success animations

Backend

  • Node.js + Express - Server framework
  • MongoDB + Mongoose - Database
  • Clerk Express - Server-side auth middleware
  • GetStream.io Node SDK - Video/chat server operations
  • Inngest - Background job processing & webhooks
  • Piston API - Code execution service

πŸ— Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Browser   β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”œβ”€β”€β”€β”€ Clerk Auth ────┐
       β”‚                    β”‚
       β”œβ”€β”€β”€β”€ React App      β”‚
       β”‚                    β–Ό
       β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚              β”‚  Clerk   β”‚
       β”‚              β”‚ (Auth)   β”‚
       β”‚              β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
       β”‚                   β”‚ Webhooks
       β–Ό                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Express API    β”‚  β”‚ Inngest  β”‚
β”‚  + MongoDB      │◄── (Events) β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚
     β”œβ”€β”€ Stream.io (Video/Chat)
     β”‚   β€’ Create calls/channels
     β”‚   β€’ Generate tokens
     β”‚   β€’ Delete resources
     β”‚
     └── Piston API
         β€’ Execute user code
         β€’ Return stdout/stderr

πŸ”„ Application Flow

1️⃣ User Registration & Authentication

User signs up
    ↓
Clerk creates account
    ↓
Clerk webhook β†’ Inngest event β†’ "clerk/user.created"
    ↓
Backend: Inngest function processes event
    ↓
MongoDB: Create User document {clerkId, name, email, profileImageUrl}
    ↓
Stream.io: Upsert user with chatClient.upsertUser()
    ↓
User can now access the app

2️⃣ Session Creation Flow

Host clicks "Create Session"
    ↓
Frontend: POST /api/sessions {problemTitle, difficulty}
    ↓
Backend:
  1. Create Session document in MongoDB
  2. Generate unique callId
  3. Create Stream video call with streamClient.video.call()
  4. Create Stream chat channel with chatClient.channel()
    ↓
Frontend: Navigate to /sessions/:sessionId
    ↓
User sees session page with video call + chat + code editor

3️⃣ Participant Joining Flow

Participant opens session URL
    ↓
Frontend: GET /api/sessions/:sessionId
    ↓
Backend: Return session data
    ↓
Frontend: Check if user is host/participant
    ↓
If neither β†’ POST /api/sessions/:sessionId/join
    ↓
Backend:
  1. Update session.participantId in MongoDB
  2. Add user to Stream chat channel
    ↓
Frontend: User can now see video + chat + code editor

4️⃣ GetStream Initialization (Video + Chat)

User enters session page
    ↓
Frontend: GET /api/chats/token
    ↓
Backend: Generate token using chatClient.createToken(clerkUserId)
    ↓
Frontend: Initialize GetStream clients
  1. Create StreamVideoClient with token
  2. Join video call: call.join()
  3. Connect chat: StreamChat.connectUser()
  4. Watch channel: channel.watch()
    ↓
Render CallAndChatUI component

5️⃣ Code Execution Flow

User writes code in Monaco editor
    ↓
User clicks "Run"
    ↓
Frontend: Send code to Piston API
  POST https://emkc.org/api/v2/piston/execute
  {language, version, files: [{name, content}]}
    ↓
Piston API: Execute code in isolated container
    ↓
Return {stdout, stderr, output, code}
    ↓
Frontend: Display output in OutputPanel
  β€’ Success β†’ Show output + confetti πŸŽ‰
  β€’ Error β†’ Show error message

6️⃣ Session End Flow (Host Only)

Host clicks "End Session"
    ↓
Frontend: POST /api/sessions/:sessionId/end
    ↓
Backend:
  1. Verify user is host
  2. Delete GetStream video call (hard delete)
  3. Delete GetStream chat channel (hard delete)
  4. Update session.status = "completed" in MongoDB
    ↓
Frontend:
  1. Leave video call: call.leave()
  2. Disconnect chat: chatClient.disconnectUser()
  3. Disconnect video: videoClient.disconnectUser()
  4. Navigate to /dashboard

πŸ“¦ Prerequisites

  • Node.js >= 18.x
  • pnpm >= 10.x (package manager)
  • MongoDB (Atlas or local)
  • Clerk Account (for authentication)
  • GetStream.io Account (for video/chat)
  • Inngest Account (for webhooks)

πŸ” Environment Variables

Frontend (.env)

VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
VITE_SERVER_BASE_URL=http://localhost:3000/api
VITE_STREAM_ACCESS_KEY=xxxxx

Backend (.env)

NODE_ENV=development
PORT=3000
DB_URL=mongodb+srv://username:[email protected]/moduo
CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
CLERK_SECRET_KEY=sk_test_xxxxx
INNGEST_EVENT_KEY=your-inngest-event-key
INNGEST_SIGNING_KEY=your-inngest-signing-key
STREAM_ACCESS_KEY=xxxxx
STREAM_ACCESS_SECRET=xxxxx
CLIENT_URL=http://localhost:5173

πŸš€ Installation & Setup

1. Clone the Repository

git clone https://github.com/KeepSerene/moduo-video-calling-interview-platform-mern.git
cd moduo

2. Install Dependencies

# Install backend dependencies
cd backend
pnpm install

# Install frontend dependencies
cd ../frontend
pnpm install

3. Set Up Environment Variables

Create .env files in both backend/ and frontend/ directories with the variables listed above.

4. Configure Clerk Webhooks

  1. Go to your Clerk dashboard
  2. Navigate to Webhooks
  3. Add endpoint: https://your-domain.com/api/inngest
  4. Subscribe to events: user.created, user.deleted
  5. Copy the webhook secret to your Inngest configuration

5. Run the Application

# From root directory - Development mode
cd backend
pnpm run dev

# In another terminal
cd frontend
pnpm run dev

The frontend will run on http://localhost:5173 and backend on http://localhost:3000.

6. Build for Production

# From root directory
pnpm run build
pnpm start

This builds the frontend and serves it from the backend Express server.


πŸ“ Project Structure (Tentative!)

moduo/
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”‚   β”œβ”€β”€ sessions.controller.js
β”‚   β”‚   β”‚   └── streamToken.controller.js
β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   β”œβ”€β”€ db.js
β”‚   β”‚   β”‚   β”œβ”€β”€ env.js
β”‚   β”‚   β”‚   β”œβ”€β”€ inngest.js
β”‚   β”‚   β”‚   └── stream.js
β”‚   β”‚   β”œβ”€β”€ middlewares/
β”‚   β”‚   β”‚   └── protectRoute.middleware.js
β”‚   β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”‚   β”œβ”€β”€ Session.js
β”‚   β”‚   β”‚   └── User.js
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”œβ”€β”€ chats.route.js
β”‚   β”‚   β”‚   └── sessions.route.js
β”‚   β”‚   └── server.js
β”‚   β”œβ”€β”€ .env
β”‚   └── package.json
β”‚
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   └── images/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”‚   └── sessions.js
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   β”œβ”€β”€ CallAndChatUI.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ CodeEditor.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ConfirmationModal.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ CreateSessionModal.jsx
β”‚   β”‚   β”‚   └── ...
β”‚   β”‚   β”œβ”€β”€ data/
β”‚   β”‚   β”‚   └── problems.js
β”‚   β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”‚   β”œβ”€β”€ useSessions.js
β”‚   β”‚   β”‚   └── useStream.js
β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   β”œβ”€β”€ axios.js
β”‚   β”‚   β”‚   β”œβ”€β”€ piston.js
β”‚   β”‚   β”‚   β”œβ”€β”€ stream.js
β”‚   β”‚   β”‚   └── utils.js
β”‚   β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”‚   β”œβ”€β”€ DashboardPage.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ HomePage.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProblemDetailsPage.jsx
β”‚   β”‚   β”‚   β”œβ”€β”€ ProblemsPage.jsx
β”‚   β”‚   β”‚   └── SessionPage.jsx
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   └── index.js
β”‚   β”‚   β”œβ”€β”€ App.jsx
β”‚   β”‚   β”œβ”€β”€ index.css
β”‚   β”‚   └── main.jsx
β”‚   β”œβ”€β”€ .env
β”‚   β”œβ”€β”€ index.html
β”‚   └── package.json
β”‚
└── package.json

🌐 API Endpoints

Sessions

Method Endpoint Description Auth Required
POST /api/sessions Create new session βœ…
GET /api/sessions/active Get all active sessions βœ…
GET /api/sessions/recent Get user's recent sessions βœ…
GET /api/sessions/:sessionId Get session by ID βœ…
POST /api/sessions/:sessionId/join Join a session βœ…
POST /api/sessions/:sessionId/end End a session (host only) βœ…

Chat

Method Endpoint Description Auth Required
GET /api/chats/token Get GetStream token for user βœ…

Webhooks

Method Endpoint Description
POST /api/inngest Inngest webhook endpoint for Clerk events

πŸ“Έ Screenshots

Dashboard

Dashboard

Session Page

Session Page


🚒 Deployment

Deploy to Render.com

  1. Create a Web Service

    • Connect your GitHub repository
    • Root directory: .
    • Build command: pnpm run build
    • Start command: pnpm start
  2. Add Environment Variables

    • Add all backend environment variables in Render dashboard
    • Set NODE_ENV=production
    • Set CLIENT_URL to your Render URL
  3. Update Clerk Webhook

    • Update webhook URL to https://your-app.onrender.com/api/inngest
  4. MongoDB Atlas

    • Whitelist Render's IP addresses or use 0.0.0.0/0 for development
    • Update DB_URL in environment variables

πŸ”‘ Key Technologies Explained

GetStream.io Architecture

Backend (Server SDK) - With API Secret:

  • βœ… Create video calls and chat channels
  • βœ… Generate user tokens
  • βœ… Delete calls/channels (hard delete)
  • βœ… Add members to channels

Frontend (Client SDK) - With User Token:

  • βœ… Connect user to video client
  • βœ… Join existing video calls
  • βœ… Connect user to chat
  • βœ… Watch chat channels
  • βœ… Render video/chat UI

Security Flow:

User β†’ Request token β†’ Backend verifies with Clerk
  ↓
Backend generates GetStream token with chatClient.createToken()
  ↓
Frontend uses token to connect to Stream
  ↓
GetStream validates token β†’ Allows connection

Inngest Integration

Inngest handles asynchronous events from Clerk webhooks:

  1. User Created: clerk/user.created

    • Creates MongoDB user document
    • Upserts GetStream.io user profile
  2. User Deleted: clerk/user.deleted

    • Removes MongoDB user document
    • Deletes GetStream.io user profile

This ensures user data stays synchronized across all services.


πŸ“„ License

This project is licensed under the Apache 2.0 License.


πŸ‘¨β€πŸ’» Author

Dhrubajyoti Bhattacharjee


πŸ™ Acknowledgments


Made with ❀️ for the developer community