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