Code together, live. A real-time collaborative coding interview platform built with the MERN stack.
- Overview
- Features
- Tech Stack
- Architecture Overview
- Application Flow
- Prerequisites
- Environment Variables
- Installation & Setup
- Project Structure
- API Endpoints
- Screenshots
- Deployment
- Key Technologies Explained
- License
- Author
- Acknowledgments
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.
- π₯ 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
- 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
- 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
βββββββββββββββ
β 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
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
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
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
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
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
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
- 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)
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
VITE_SERVER_BASE_URL=http://localhost:3000/api
VITE_STREAM_ACCESS_KEY=xxxxxNODE_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:5173git clone https://github.com/KeepSerene/moduo-video-calling-interview-platform-mern.git
cd moduo# Install backend dependencies
cd backend
pnpm install
# Install frontend dependencies
cd ../frontend
pnpm installCreate .env files in both backend/ and frontend/ directories with the variables listed above.
- Go to your Clerk dashboard
- Navigate to Webhooks
- Add endpoint:
https://your-domain.com/api/inngest - Subscribe to events:
user.created,user.deleted - Copy the webhook secret to your Inngest configuration
# From root directory - Development mode
cd backend
pnpm run dev
# In another terminal
cd frontend
pnpm run devThe frontend will run on http://localhost:5173 and backend on http://localhost:3000.
# From root directory
pnpm run build
pnpm startThis builds the frontend and serves it from the backend Express server.
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
| 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) | β |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/chats/token |
Get GetStream token for user | β |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/inngest |
Inngest webhook endpoint for Clerk events |
-
Create a Web Service
- Connect your GitHub repository
- Root directory:
. - Build command:
pnpm run build - Start command:
pnpm start
-
Add Environment Variables
- Add all backend environment variables in Render dashboard
- Set
NODE_ENV=production - Set
CLIENT_URLto your Render URL
-
Update Clerk Webhook
- Update webhook URL to
https://your-app.onrender.com/api/inngest
- Update webhook URL to
-
MongoDB Atlas
- Whitelist Render's IP addresses or use
0.0.0.0/0for development - Update
DB_URLin environment variables
- Whitelist Render's IP addresses or use
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 handles asynchronous events from Clerk webhooks:
-
User Created:
clerk/user.created- Creates MongoDB user document
- Upserts GetStream.io user profile
-
User Deleted:
clerk/user.deleted- Removes MongoDB user document
- Deletes GetStream.io user profile
This ensures user data stays synchronized across all services.
This project is licensed under the Apache 2.0 License.
Dhrubajyoti Bhattacharjee
- GetStream.io - Video and chat infrastructure
- Clerk - Authentication
- Piston - Code execution engine
- Inngest - Background jobs
- Monaco Editor - Code editor
Made with β€οΈ for the developer community

