A Google Docs-style real-time collaborative text editor where multiple users can simultaneously edit the same document with live cursor tracking, presence indicators, and conflict-free synchronization.
- Frontend: [Add deployed URL]
- Backend: [Add deployed URL]
- Video Demo: [Add YouTube/Google Drive link]
- Tech Stack
- Architecture Overview
- Features
- Project Structure
- Setup Instructions
- Environment Variables
- API Endpoints
- WebSocket Protocol
- AI Tools Used
- Deployment Guide
- Known Limitations
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite, Tailwind CSS |
| Editor | TipTap v3 (ProseMirror-based), y-prosemirror |
| Real-Time | Yjs (CRDT), y-websocket, y-protocols, WebSocket (ws) |
| Backend | Node.js, Express, TypeScript, tsx |
| Database | PostgreSQL with Prisma ORM |
| Logging | Pino (structured JSON logging) |
| Monorepo | pnpm workspaces |
| Icons | Lucide React |
| HTTP Client | Axios |
| AI | OpenAI GPT-4o-mini (writing assistant) |
┌─────────────────────────────────────────────────────────┐
│ Client (React + Vite) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────────┐ │
│ │ Dashboard │ │ Editor │ │ Presence Indicators │ │
│ │ (CRUD) │ │ (TipTap) │ │ (Avatar Stack) │ │
│ └─────┬─────┘ └─────┬─────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ │ ┌────┴────┐ │ │
│ │ │ Yjs │───────────────┘ │
│ │ │ (CRDT) │ │
│ │ └────┬────┘ │
└────────┼──────────────┼──────────────────────────────────┘
│ REST API │ WebSocket
│ (Axios) │ (y-websocket)
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Server (Express + ws) │
│ │
│ ┌────────────┐ ┌─────────────────────────────────┐ │
│ │ REST API │ │ WebSocket Server │ │
│ │ /api/* │ │ - Sync protocol (Yjs) │ │
│ │ - Documents │ │ - Awareness protocol (cursors) │ │
│ │ - Users │ │ - Room management │ │
│ │ - Revisions │ │ - Debounced persistence │ │
│ │ - AI ◆──────┼──┼───────────► OpenAI API │ │
│ └──────┬──────┘ └──────────────┬──────────────────┘ │
│ │ │ │
│ └────────┬───────────────┘ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Prisma ORM │ │
│ └───────┬────────┘ │
└─────────────────┼───────────────────────────────────────┘
▼
┌────────────────┐
│ PostgreSQL │
│ - Users │
│ - Documents │
│ - Revisions │
└────────────────┘
-
CRDT-based conflict resolution: The editor uses Yjs, a high-performance CRDT (Conflict-free Replicated Data Type) implementation. Every client maintains a local Yjs document that merges changes automatically without conflicts.
-
WebSocket transport: Clients connect via
y-websocketto the server's/collab/:documentIdendpoint. The server maintains in-memory "rooms" per document, each holding a Yjs document instance. -
Sync protocol: On connection, the server sends the full document state (SyncStep1). Subsequent edits are broadcast as incremental Yjs updates to all other connected clients, excluding the origin.
-
Awareness protocol: Cursor positions, user names, and colors are shared through Yjs awareness, enabling live cursor tracking and presence indicators.
-
Persistence: Document state is debounced (2 seconds) and saved to PostgreSQL as a serialized Yjs binary. Periodic snapshots are created every 50 updates for revision history. When the last client disconnects, a final save is triggered before the room is destroyed.
- Real-time collaborative editing - Multiple users edit simultaneously with instant sync
- Live cursor tracking - See other users' cursors with colored indicators
- User presence - Avatar stack showing who is online in a document
- Rich text formatting - Bold, italic, underline, strikethrough, inline code, headings (H1/H2), bullet lists, ordered lists, blockquotes
- Document management - Create, rename, delete documents from a dashboard
- Revision history - Browse past revisions and restore documents to earlier states
- Conflict-free editing - CRDT guarantees eventual consistency without manual conflict resolution
- Persistent storage - Documents are saved to PostgreSQL and survive server restarts
- AI Writing Assistant - Built-in AI panel powered by OpenAI:
- Summarize - Condense selected text or the full document
- Improve Writing - Enhance clarity, readability, and flow
- Continue Writing - AI generates the next sentences matching your style
- Fix Grammar - Correct spelling, grammar, and punctuation
- Change Tone - Rewrite in Professional, Casual, Friendly, Academic, or Concise tone
- Translate - Translate text to Spanish, French, German, Hindi, Japanese, Chinese, or Tamil
- Lazy-loaded editor - Editor page is code-split for faster initial load
- Responsive UI - Clean, modern interface built with Tailwind CSS
collab-editor/
├── client/ # React frontend (Vite)
│ ├── src/
│ │ ├── components/
│ │ │ ├── common/ # Button, LoadingSpinner, Modal
│ │ │ ├── documents/ # DocumentList, DocumentCard, CreateDocumentModal
│ │ │ ├── editor/ # Editor, Toolbar, AIPanel, RevisionPanel, EditorFooter
│ │ │ ├── layout/ # AppLayout, Header, Sidebar
│ │ │ └── presence/ # AvatarStack, PresenceIndicator
│ │ ├── hooks/
│ │ │ ├── useAI.ts # OpenAI-powered writing assistant
│ │ │ ├── useCollaboration.ts # Yjs doc + WebSocket provider setup
│ │ │ ├── useDocuments.ts # Document CRUD operations
│ │ │ ├── usePresence.ts # Awareness-based user presence
│ │ │ ├── useRevisions.ts # Revision history fetch/restore
│ │ │ └── useUser.ts # User registration and session
│ │ ├── lib/
│ │ │ ├── api.ts # Axios instance with auth headers
│ │ │ └── colors.ts # User color/initials utilities
│ │ ├── pages/
│ │ │ ├── DashboardPage.tsx # Document listing + creation
│ │ │ ├── EditorPage.tsx # Collaborative editor view
│ │ │ └── NotFoundPage.tsx # 404 page
│ │ └── App.tsx # Root component with routing
│ ├── vercel.json # SPA rewrite rules for Vercel
│ └── vite.config.ts # Vite config with chunk splitting
│
├── server/ # Express + WebSocket backend
│ ├── src/
│ │ ├── config/env.ts # Environment variable loading
│ │ ├── middleware/errorHandler.ts # Express error middleware
│ │ ├── routes/
│ │ │ ├── ai.ts # AI writing assistant endpoint
│ │ │ ├── documents.ts # Document CRUD endpoints
│ │ │ ├── revisions.ts # Revision history + restore
│ │ │ ├── users.ts # User registration + lookup
│ │ │ └── index.ts # Route aggregator
│ │ ├── services/
│ │ │ ├── aiService.ts # OpenAI integration logic
│ │ │ ├── documentService.ts # Document business logic
│ │ │ ├── revisionService.ts # Revision business logic
│ │ │ └── userService.ts # User business logic
│ │ ├── utils/
│ │ │ ├── prisma.ts # Prisma client singleton
│ │ │ └── logger.ts # Pino logger instance
│ │ ├── websocket/
│ │ │ ├── server.ts # WebSocket server, rooms, sync
│ │ │ └── persistence.ts # Debounced DB persistence
│ │ └── index.ts # Express app entry point
│ └── prisma/
│ └── schema.prisma # Database schema
│
├── packages/
│ └── shared/ # Shared TypeScript types and constants
│ └── src/
│ ├── types/ # Document, User type definitions
│ └── constants/ # Shared constants
│
├── pnpm-workspace.yaml # Monorepo workspace config
├── .env.example # Environment variable template
└── package.json # Root scripts (dev, build)
- Node.js >= 18
- pnpm (install via
npm install -g pnpm) - PostgreSQL running locally or a hosted instance
git clone https://github.com/<your-username>/collab-editor.git
cd collab-editorpnpm installCopy the example env files and fill in your values:
# Root-level (reference)
cp .env.example .env
# Server
cp server/.env.example server/.env
# Client
cp client/.env.example client/.envEdit server/.env with your PostgreSQL connection string and OpenAI API key (see Environment Variables).
# Generate Prisma client
pnpm --filter server db:generate
# Run database migrations
pnpm --filter server db:migratepnpm --filter shared build# Start both client and server concurrently
pnpm devOr start them individually:
pnpm dev:server # Express + WebSocket on http://localhost:3001
pnpm dev:client # Vite dev server on http://localhost:5173Navigate to http://localhost:5173 in your browser. Enter a name and email to get started, then create a document and start editing. Open a second browser tab/window to see real-time collaboration in action.
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgresql://user:password@localhost:5432/collab_editor |
PORT |
Server port | 3001 |
CLIENT_URL |
Frontend URL (for CORS) | http://localhost:5173 |
NODE_ENV |
Environment mode | development |
OPENAI_API_KEY |
OpenAI API key (for AI assistant) | (required for AI features) |
| Variable | Description | Default |
|---|---|---|
VITE_API_URL |
Backend REST API base URL | http://localhost:3001/api |
VITE_WS_URL |
Backend WebSocket URL | ws://localhost:3001 |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/users |
Register / login a user |
| GET | /api/users/me |
Get current user (by x-user-id header) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/documents |
List user's documents |
| POST | /api/documents |
Create a new document |
| GET | /api/documents/:id |
Get document by ID |
| PATCH | /api/documents/:id |
Update document title |
| DELETE | /api/documents/:id |
Delete a document |
| POST | /api/documents/:id/join |
Join a document as a collaborator |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/documents/:id/revisions |
List document revisions |
| GET | /api/documents/:id/revisions/:revId |
Get a specific revision |
| POST | /api/documents/:id/revisions/:revId/restore |
Restore to a revision |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/ai |
AI writing assistant. Body: { action, text, tone?, language? } |
Supported actions: summarize, improve, continue, fix-grammar, change-tone, translate
| Endpoint | Description |
|---|---|
ws://<host>/collab/:documentId |
Yjs sync + awareness protocol for real-time editing |
The WebSocket server implements two Yjs sub-protocols:
-
Sync Protocol (msg type 0): Handles document state exchange. On connect, the server sends
SyncStep1(state vector). The client responds withSyncStep2(missing updates). Subsequent edits are broadcast as incremental updates. -
Awareness Protocol (msg type 1): Carries user presence data (name, color, cursor position). Updates are broadcast to all connected clients in the same room.
A heartbeat ping/pong runs every 30 seconds to detect and clean up stale connections.
The editor includes a built-in AI Writing Assistant panel powered by OpenAI's GPT-4o-mini model. Users can select text (or use the full document) and invoke AI actions directly from the editor toolbar. The server proxies requests to the OpenAI API via the /api/ai endpoint.
Claude was used as the primary AI coding assistant during development, running locally via Ollama for privacy and offline access. It assisted with:
- Architecting the monorepo structure and choosing the CRDT approach over OT
- Designing the WebSocket room management and persistence strategy
- Writing and debugging the Yjs integration with TipTap v3 (ProseMirror plugin bridging)
- Structuring the Prisma schema for documents, users, and revision history
- Code review and refactoring suggestions
Codex was used as a supplementary AI tool for:
- Generating boilerplate code for Express routes and React components
- Autocompleting repetitive patterns (Prisma service functions, React hooks)
- Writing TypeScript type definitions for the shared package
- Suggesting Tailwind CSS class combinations for UI components
The application is split into two deployable units:
| Part | Platform | Reason |
|---|---|---|
| Client | Vercel | Free, fast static/SPA hosting; already has vercel.json |
| Server | Render | Free tier, supports WebSockets + long-running processes |
| Database | Render PostgreSQL | Provision alongside the server with zero extra config |
Why not Vercel for the server? The server uses persistent WebSocket connections for real-time collaboration. Vercel Functions are request/response-based and do not support long-lived WebSocket connections.
-
Create a Render account and go to the Dashboard.
-
Create a PostgreSQL database:
- Dashboard > New > PostgreSQL
- Name:
collab-editor-db - Plan: Free
- Click Create Database
- After creation, copy the Internal Database URL
-
Deploy the server as a Web Service:
- Dashboard > New > Web Service
- Connect your GitHub repository
- Configure the service:
Setting Value Name collab-editor-apiRoot Directory serverRuntime Node Build Command pnpm install && pnpm buildStart Command pnpm start- Add the following Environment Variables:
Variable Value DATABASE_URL(paste the Internal Database URL from step 2) CLIENT_URLhttps://your-app.vercel.app(update after Vercel deploy)PORT10000NODE_ENVproductionOPENAI_API_KEY(your OpenAI API key) -
Click Create Web Service. After deployment completes, note the service URL (e.g.
https://collab-editor-api.onrender.com).
The
startscript (prisma migrate deploy && node dist/index.js) automatically runs pending database migrations on every deploy.
-
Create a production env file at
client/.env.production:VITE_API_URL=https://collab-editor-api.onrender.com/api VITE_WS_URL=wss://collab-editor-api.onrender.comUse
wss://(notws://) in production — Render provides TLS automatically. -
Option A — Deploy via Vercel Dashboard:
- Go to vercel.com and import your GitHub repository
- Set Root Directory to
client - Framework Preset: Vite
- Add environment variables:
VITE_API_URLandVITE_WS_URLwith the Render server URLs - Click Deploy
-
Option B — Deploy via CLI:
npm i -g vercel cd client vercelWhen prompted, confirm the root directory and it will detect
vercel.jsonautomatically. -
After deploy, copy the Vercel URL (e.g.
https://collab-editor.vercel.app) and go back to Render to update theCLIENT_URLenvironment variable so CORS allows requests from your frontend.
- Open the Vercel URL in two browser tabs
- Register as two different users
- Create a document and open it in both tabs
- Type in one tab — changes should appear in the other in real time
- Click the AI button in the toolbar and test a feature like Summarize
If you prefer hosting everything in one place, Railway supports both Node.js services and PostgreSQL with WebSocket support:
- Create a new project on Railway
- Add a PostgreSQL plugin — copy the
DATABASE_URL - Add a Node.js service, set root directory to
server, and configure the same env vars - Deploy the client separately on Vercel (or use Railway's static site hosting)
-
No authentication/authorization: Users are identified by a simple name + email registration stored in
localStorage. There is no password, OAuth, or JWT-based auth. Any user can access any document by ID. -
Single-server WebSocket: The WebSocket server holds document rooms in memory. This means horizontal scaling (multiple server instances) is not supported without adding a pub/sub layer (e.g., Redis) for cross-instance synchronization.
-
No offline editing: If the WebSocket connection drops, edits are not queued locally. The user must be online for changes to sync. Yjs supports offline-first patterns, but this is not implemented.
-
Basic revision history: Revisions are stored as Yjs binary blobs. The UI shows a list of revisions with timestamps but does not display a visual diff between versions. Restoring a revision requires a page reload to see the changes.
-
No granular permissions: All users who join a document have full edit access. There are no read-only viewers, document ownership restrictions, or sharing permissions.
-
No rate limiting: The REST API and WebSocket server do not enforce rate limits, making them vulnerable to abuse in a production environment.
-
User session is per-browser: User identity is stored in
localStorage, so it does not persist across different browsers or devices. Clearing browser data loses the session. -
No rich media support: The editor supports text formatting only. Images, tables, embeds, and file attachments are not supported.
-
Debounced persistence: Document changes are saved to the database with a 2-second debounce. If the server crashes within this window, the most recent edits may be lost.
| Command | Description |
|---|---|
pnpm dev |
Start client + server concurrently in dev mode |
pnpm dev:client |
Start only the Vite dev server |
pnpm dev:server |
Start only the Express server (with hot reload) |
pnpm build |
Build shared package, then client and server |
pnpm --filter server db:migrate |
Run Prisma migrations |
pnpm --filter server db:generate |
Regenerate Prisma client |
pnpm --filter server db:studio |
Open Prisma Studio (DB browser) |
This project was built as a submission for the HCL x GUVI hackathon.