A real-time collaborative code editor built for pair programming and technical interviews. Multiple users can write, edit, and execute code simultaneously in the same room with live cursor presence, conflict-free sync, and shared output.
Live Demo: https://collabcode-livid.vercel.app
- Real-time sync : Multiple users edit the same file simultaneously. Changes appear instantly across all connected tabs using CRDTs (Conflict-free Replicated Data Types) via Yjs.
- Live cursor presence : See where every collaborator's cursor is in real time, with colored labels showing their username.
- Code execution : Run JavaScript, TypeScript, and Python directly in the browser. Output is broadcast to everyone in the room simultaneously.
- Room-based sessions : Create a room and share the URL. Anyone with the link can join and collaborate instantly.
- Persistent state : New users joining an existing room immediately see the current code and last output — no manual sync needed.
- Authentication : Register and log in with email and password. JWT-based sessions persist across browser reloads.
- Language switching : Switch between JavaScript, TypeScript, and Python mid-session.
| Technology | Purpose |
|---|---|
| Next.js 15 + TypeScript | Framework and routing |
| Monaco Editor | VS Code's editor, embedded in the browser |
| Yjs | CRDT library for conflict-free real-time sync |
| Socket.io Client | WebSocket connection to backend |
| Tailwind CSS v4 | Styling |
| Technology | Purpose |
|---|---|
| Node.js + Express | HTTP server and REST API |
| Socket.io | WebSocket server for real-time events |
| Yjs | Server-side document state management |
| Prisma ORM | Database access layer |
| PostgreSQL | Persistent storage for users and rooms |
| bcryptjs | Password hashing |
| JSON Web Tokens | Authentication |
| child_process | Sandboxed code execution |
┌─────────────────────────────────────┐
│ Next.js Frontend │
│ Monaco Editor + Yjs CRDT client │
└────────────────┬────────────────────┘
│ WebSocket (Socket.io)
┌────────────────▼────────────────────┐
│ Node.js / Express │
│ Socket.io server + Yjs document │
│ REST API (auth, code execution) │
└──────────┬──────────────────────────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ (Prisma) │
└─────────────┘
When a user types a character:
- Monaco Editor detects the change
- MonacoBinding updates the shared Yjs document
- Yjs creates a binary update packet (position + content + metadata)
- Socket.io sends the update to the backend
- Backend applies the update to its own Yjs document and broadcasts to all room members
- Each client's Yjs receives the update and resolves any conflicts automatically
- Monaco Editor reflects the change — under 100ms end-to-end
Operational Transformation (used by Google Docs) requires a central server to coordinate all operations. CRDTs resolve conflicts by design at the data structure level — making them more suitable for distributed systems and offline-first scenarios. Yjs implements a variant of the YATA algorithm which provides O(1) conflict resolution.
User clicks Run
↓
Frontend sends POST /execute to backend
↓
Backend writes code to a temp file
↓
Backend spawns child process: node tempfile.js
↓
Captures stdout + stderr (10s timeout)
↓
Returns result to frontend via HTTP
↓
Frontend emits result via Socket.io to room
↓
Backend saves output to room state
↓
All connected clients receive and display output
- Node.js 18+
- Docker Desktop (for PostgreSQL)
- Git
git clone https://github.com/VeerRaj-007/collabcode.git
cd collabcodedocker run --name collabcode-db \
-e POSTGRES_USER=collabcode \
-e POSTGRES_PASSWORD=collabcode123 \
-e POSTGRES_DB=collabcode \
-p 5432:5432 \
-d postgrescd backend
npm installCreate .env:
PORT=5000
DATABASE_URL="postgresql://collabcode:collabcode123@localhost:5432/collabcode"
JWT_SECRET="your_secret_key"
Run migrations and start:
npx prisma migrate dev
npm run devcd frontend
npm installCreate .env.local:
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000
Start:
npm run devGo to http://localhost:3000, register an account, and create a room.
Open the same room URL in a second browser tab to test real-time collaboration.
| Service | Platform |
|---|---|
| Frontend | Vercel |
| Backend | Render |
| Database | Render PostgreSQL |
collabcode/
├── frontend/
│ └── src/
│ ├── app/
│ │ ├── editor/[roomId]/
│ │ │ ├── page.tsx # Dynamic editor route
│ │ │ ├── EditorComponent.tsx # Monaco + Yjs + Socket.io
│ │ │ └── OutputPanel.tsx # Execution output
│ │ ├── login/page.tsx
│ │ ├── register/page.tsx
│ │ └── page.tsx # Home / room creation
│ ├── context/
│ │ └── AuthContext.tsx # JWT auth state
│ └── lib/
│ ├── socket.ts # Socket.io singleton
│ └── piston.ts # Code execution API
└── backend/
├── src/
│ ├── index.ts # Express + Socket.io server
│ ├── prisma.ts # Prisma client
│ └── routes/
│ └── auth.ts # Register / login / me
└── prisma/
└── schema.prisma # User, Room, RoomParticipant
Why separate frontend and backend? Socket.io requires a persistent, stateful WebSocket connection. Next.js API routes are stateless and terminate after each request — making them unsuitable for real-time connections.
Why Yjs over a custom sync solution? Building a correct CRDT or OT implementation from scratch is a research-level problem. Yjs is a battle-tested library used in production by tools like Jupyter and ProseMirror. Using it lets us focus on the product layer.
Why child_process for code execution? For a portfolio-scale project, spinning up a sandboxed process per execution is sufficient. Production-scale tools like Replit use per-user Docker containers — a natural next step.
Veer Raj — GitHub
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.