Real-time collaborative code editor with integrated WebRTC video calling for pair programming and technical interviews.
| Feature | Details |
|---|---|
| Collaborative Editor | Monaco Editor with live code sync, shared cursors, language switching |
| Video / Audio | Peer-to-peer WebRTC (camera, mic, screen share) |
| Chat | Real-time room chat with typing indicators |
| Code Execution | Local Docker-based execution engine with sandboxed containers for Node.js, Python, Java, and C++ |
| Room System | Create / join rooms by ID, optional access-code for private rooms |
| Interview Mode | Timer, problem statement panel, private interviewer notes |
| Auth | Register / Login / Guest access (JWT) |
| Persistence | Code auto-saved to MongoDB every 5 s, restored on reconnect |
| Reconnection | Socket.io auto-reconnect, WebRTC ICE restart on failure |
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| Styling | Tailwind CSS |
| Editor | Monaco Editor (@monaco-editor/react) |
| State | Zustand |
| Real-time | Socket.io |
| Video | WebRTC (native browser API) |
| Backend | Node.js, Express, TypeScript |
| Database | MongoDB (Mongoose) |
| Code Exec | Local Docker Containers |
CollabCode/
βββ backend/
β βββ src/
β β βββ config/
β β β βββ database.ts # MongoDB connection
β β βββ models/
β β β βββ ExecutionJob.ts # Queued room execution jobs + polling state
β β β βββ User.ts # User model (bcrypt hashed passwords)
β β β βββ Room.ts # Room model (code, messages, settings)
β β βββ controllers/
β β β βββ code.ts # /api/code/run controllers
β β βββ routes/
β β β βββ auth.ts # /api/auth (register, login, guest)
β β β βββ code.ts # /api/code/run (queue + polling)
β β β βββ rooms.ts # /api/rooms (CRUD)
β β β βββ execute.ts # /api/execute legacy compatibility route
β β βββ socket/
β β β βββ index.ts # All Socket.io event handlers
β β βββ services/
β β β βββ codeExecutor.ts # Export wrapper for executeCode
β β β βββ dockerExecutor.ts # Docker-based compilation and execution engine
β β β βββ executionQueue.ts # In-memory worker queue for room runs
β β βββ middleware/
β β β βββ auth.ts # JWT middleware
β β βββ index.ts # Express + Socket.io server
β βββ .env.example
β βββ package.json
β βββ tsconfig.json
β
βββ frontend/
βββ src/
β βββ components/
β β βββ editor/
β β β βββ CodeEditor.tsx # Monaco + collaboration logic
β β β βββ LanguageSelector.tsx
β β βββ video/
β β β βββ VideoGrid.tsx # Video tiles + media controls
β β β βββ VideoTile.tsx # Individual participant tile
β β βββ chat/
β β β βββ ChatPanel.tsx # Chat + typing indicators
β β βββ console/
β β β βββ ConsolePanel.tsx # Stdin + execution output
β β βββ room/
β β β βββ ParticipantList.tsx
β β βββ interview/
β β β βββ ProblemPanel.tsx # Problem statement
β β β βββ InterviewControls.tsx # Timer + private notes (host)
β β βββ layout/
β β βββ MainLayout.tsx # Workspace layout (3-panel)
β βββ hooks/
β β βββ useSocket.ts # Socket.io event listeners + actions
β β βββ useWebRTC.ts # WebRTC peer mesh management
β βββ pages/
β β βββ Auth.tsx # Login / Register / Guest
β β βββ Home.tsx # Create / Join room
β β βββ Room.tsx # Room entry point
β βββ services/
β β βββ api.ts # Axios API client
β β βββ socket.ts # Socket.io client singleton
β βββ store/
β β βββ useStore.ts # Zustand global store
β βββ types/
β βββ index.ts # Shared TypeScript types
βββ .env.example
βββ package.json
βββ tailwind.config.js
βββ vite.config.ts
- Node.js 18+
- MongoDB (local or MongoDB Atlas)
- Docker (must be installed and running for the local code execution engine)
# Backend
cd backend
npm install
# Frontend
cd ../frontend
npm install# Backend
cp backend/.env.example backend/.env
# Edit backend/.env with your values:
# MONGODB_URI, JWT_SECRET
# Frontend
cp frontend/.env.example frontend/.envmongod --dbpath /data/db
# or with Docker:
docker run -d -p 27017:27017 mongo:7cd backend
npm run dev
# Server runs on http://localhost:4000cd frontend
npm run dev
# App runs on http://localhost:5173Open http://localhost:5173 in your browser.
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 4000 |
Server port |
MONGODB_URI |
Yes | β | MongoDB connection string |
JWT_SECRET |
Yes | β | JWT signing secret |
JWT_EXPIRES_IN |
No | 7d |
Token expiry |
FRONTEND_URL |
No | http://localhost:5173 |
CORS origin |
DOCKER_MEMORY_LIMIT |
No | 256m |
Memory limit per execution container |
DOCKER_CPU_LIMIT |
No | 0.5 |
CPU limit per execution container |
DOCKER_PIDS_LIMIT |
No | 64 |
Process ID limit per execution container |
Additional execution settings:
CODE_RUNNER_CONCURRENCY: number of room execution jobs processed in parallelEXECUTION_JOB_TTL_HOURS: how long execution job documents stay in MongoDB
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_BACKEND_URL |
No | http://localhost:4000 |
Backend Socket.io URL |
In development the Vite proxy forwards
/apirequests to the backend automatically.
Monaco's onChange is debounced at 80 ms before emitting a code-change event.
The server broadcasts the new code to all other participants.
A isRemoteChange flag prevents re-broadcasting remote changes back to the server.
For teams > 4 or high-frequency typing, upgrade to Yjs + y-monaco (CRDT):
npm install yjs y-monaco y-websocketEach participant creates a direct RTCPeerConnection to every other participant.
This works well up to ~6 users (15 connections at max). For larger rooms, add a Selective Forwarding Unit (SFU) like mediasoup or LiveKit.
Signalling flow:
User A joins room
ββ server sends 'existing-peers' list to A
ββ A creates RTCPeerConnection for each existing peer
ββ A sends webrtc-offer β server β peer
ββ peer sends webrtc-answer β server β A
ββ ICE candidates exchanged β media flows
Active user presence is held in memory on the server using a Map<roomId, ActiveUser[]>.
Code is persisted to MongoDB every 5 seconds using a debounced scheduleSave().
On reconnect, joining the room re-sends the full room-state event with the latest code.
Primary room execution flow:
POST /api/code/runqueues an execution job withroomId,sourceCode,language,languageId, andstdin- The backend worker spins up or reuses a Docker container configured for the submitted language.
- The source code is compiled (if necessary) and executed against the local Docker instance via spawn.
GET /api/code/run/:executionIdreturns the execution status along with standard outputs and metrics.- The frontend console displays stdout, stderr, compile output, status, time, and memory once the execution completes.
Primary execution routes:
POST /api/code/runGET /api/code/run/:executionId
The room UI now uses the queued REST flow above. The older /api/execute route and socket execution events remain as legacy paths mapping to the new Docker executor.
| Mode | When | Safety |
|---|---|---|
| Local Docker | Room runs and direct compatibility executes | Sandboxed lightweight local containers spawned with restrictive memory & CPU bounds |
The current room execution system relies on the local Docker engine. Ensure Docker is running in the background before interacting with execution features.
| Event | Payload | Description |
|---|---|---|
join-room |
{ roomId, accessCode? } |
Join a room |
leave-room |
{ roomId } |
Leave a room |
code-change |
{ roomId, code, version } |
Broadcast code update |
cursor-change |
{ roomId, position } |
Broadcast cursor position |
language-change |
{ roomId, language } |
Change editor language |
send-message |
{ roomId, text } |
Send chat message |
typing |
{ roomId } |
Typing indicator |
execute-code |
{ roomId, code, language, stdin } |
Legacy compatibility run event |
toggle-interview-mode |
{ roomId, enabled } |
Host: toggle interview mode |
update-problem |
{ roomId, problem } |
Host: update problem statement |
update-notes |
{ roomId, notes } |
Host: update private notes |
update-timer |
{ roomId, seconds } |
Host: sync timer |
webrtc-offer |
{ to, offer } |
WebRTC offer (relay) |
webrtc-answer |
{ to, answer } |
WebRTC answer (relay) |
webrtc-ice-candidate |
{ to, candidate } |
ICE candidate (relay) |
media-state-change |
{ roomId, videoOn, audioOn } |
Camera/mic toggle |
| Event | Payload | Description |
|---|---|---|
room-state |
{ room, participants, isHost } |
Full state on join |
user-joined |
{ participant } |
New participant joined |
user-left |
{ socketId, userId } |
Participant left |
existing-peers |
{ peers } |
Existing peers for WebRTC init |
code-updated |
{ code, version, authorId } |
Remote code change |
cursor-updated |
{ userId, username, position } |
Remote cursor |
language-changed |
{ language } |
Language changed |
message-received |
{ userId, username, text, timestamp } |
New chat message |
user-typing |
{ userId, username } |
Typing indicator |
execution-started |
β | Legacy compatibility signal for direct socket execution |
execution-result |
ExecutionResult |
Legacy compatibility result for direct socket execution |
interview-mode-changed |
{ enabled } |
Interview mode toggled |
problem-updated |
{ problem } |
Problem statement changed |
timer-updated |
{ seconds } |
Timer synced |
webrtc-offer |
{ from, offer, fromUser } |
Forwarded offer |
webrtc-answer |
{ from, answer } |
Forwarded answer |
webrtc-ice-candidate |
{ from, candidate } |
Forwarded ICE candidate |
peer-media-changed |
{ socketId, videoOn, audioOn } |
Peer media state |
- Yjs CRDT β conflict-free collaborative editing at scale
- SFU (mediasoup / LiveKit) β video for 10+ participants
- Recording β save sessions to S3
- Themes β light mode, custom editor themes
- AI hints β GPT-powered code suggestions in the problem panel
- Test runner β unit test support alongside code execution
- Room history β time-travel through code snapshots
- OAuth β GitHub / Google login
MIT