A real-time collaborative document editor, similar to Google Docs. Multiple users can edit the same document simultaneously with live cursors, comments, version history, and offline support.
Built with CRDTs (Conflict-free Replicated Data Types) for conflict-free real-time synchronization.
┌─────────────────────────────────────────────────────────────┐
│ Docker Network │
│ │
│ ┌──────────────┐ WebSocket ┌───────────────────────┐ │
│ │ Next.js │◄──────────────►│ Collaboration Server │ │
│ │ Frontend │ (Yjs sync) │ (y-websocket) │ │
│ │ :3000 │ │ :1234 │ │
│ │ │ REST/HTTP ├───────────────────────┤ │
│ │ │◄──────────────►│ Backend API │ │
│ │ │ │ (Express + TS) │ │
│ └──────────────┘ │ :4000 │ │
│ └──────────┬────────────┘ │
│ │ SQL │
│ ┌──────────▼────────────┐ │
│ │ PostgreSQL :5432 │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Component | Port | Description |
|---|---|---|
| Frontend | 3000 | Next.js app with Tiptap collaborative editor |
| Backend API | 4000 | Express REST API for documents, comments, versions, auth |
| Collaboration Server | 1234 | y-websocket server for real-time CRDT synchronization |
| PostgreSQL | 5432 | Persistent storage for documents, users, comments, versions |
- Editing: User types → Tiptap updates local Y.Doc → y-websocket sends delta → Collaboration Server broadcasts to all clients → CRDTs guarantee convergence
- Persistence: Collaboration Server serializes Y.Doc state → stores in PostgreSQL
- Metadata: Document CRUD, comments, versions, auth → REST API
- Awareness: Cursor positions, user names/colors → Yjs awareness protocol over WebSocket
CRDTs (Conflict-free Replicated Data Types) are data structures designed so that replicas can be modified independently and concurrently, always converging to the same state when synced — no central authority needed.
Yjs uses the YATA (Yet Another Transformation Approach) algorithm:
- Y.Doc contains named shared types (
Y.Text,Y.XmlFragment,Y.Array,Y.Map) - Unique operation IDs: Every operation gets
(clientID, clock)— globally unique, total ordering - Insert resolution: Each insert carries left and right "origin" references. When two inserts target the same position, the one with the smaller
clientIDgoes first. This is deterministic — all clients converge regardless of operation arrival order - Commutativity + Idempotency: Operations produce the same result in any order, and duplicates are no-ops
- Tombstoning: Deleted items are marked (not removed), so late-arriving inserts can still reference them
- Offline sync: Edits accumulate locally in Y.Doc + IndexedDB. On reconnect, state vectors enable efficient diff exchange
The math guarantees convergence. Two users typing at the same position get a deterministic ordering. Two users editing different parts merge trivially. No "conflict resolution dialog" is ever needed. Offline edits queue locally and merge seamlessly on reconnect via state vector diffing.
- Real-time collaborative editing — Multiple users edit simultaneously with instant sync
- Collaborative cursors — See other users' cursor positions and selections with name labels
- Comments — Add comments to documents with threading and resolution
- Version history — Save snapshots and restore previous versions
- Offline editing — Edit without internet; changes sync automatically on reconnect (via IndexedDB)
- Conflict resolution — Automatic via CRDTs — no manual merge needed, ever
| Layer | Technology |
|---|---|
| Frontend | Next.js 14, React 18, TypeScript |
| Editor | Tiptap (ProseMirror) with collaborative extensions |
| CRDT | Yjs with y-websocket, y-indexeddb, y-prosemirror |
| Backend API | Express, TypeScript, Zod validation |
| Database | PostgreSQL 16, Knex.js query builder |
| Auth | JWT (bcryptjs for password hashing) |
| Infrastructure | Docker Compose |
| CI/CD | GitHub Actions |
# Clone and start everything
make docker-up
# Services:
# Frontend: http://localhost:3000
# Backend API: http://localhost:4000
# Collab Server: ws://localhost:1234
# PostgreSQL: localhost:5432Prerequisites: Node.js 20+, PostgreSQL 16+
# 1. Install dependencies
make install
# 2. Create the database
createdb collab_editor
# 3. Run migrations
make migrate
# 4. Start all services
make dev- Open http://localhost:3000
- Register a new account
- Create a document
- Open the same document URL in another browser/incognito tab
- Log in with a different account (or the same one)
- Type in both tabs — edits sync in real-time with colored cursors!
collab-editor/
├── package.json # npm workspaces root
├── tsconfig.base.json # Shared TypeScript config
├── docker-compose.yml # All services
├── Makefile # Common commands
├── .github/workflows/ci.yml # GitHub Actions CI
├── packages/
│ ├── shared/ # Shared types and constants
│ ├── database/ # Knex migrations
│ ├── backend/ # Express REST API
│ ├── collaboration/ # y-websocket CRDT server
│ └── frontend/ # Next.js app
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register new user |
| POST | /api/auth/login |
Login, returns JWT |
| GET | /api/auth/me |
Get current user |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/documents |
List user's documents |
| POST | /api/documents |
Create document |
| GET | /api/documents/:id |
Get document |
| PATCH | /api/documents/:id |
Update title |
| DELETE | /api/documents/:id |
Delete (owner only) |
| POST | /api/documents/:id/share |
Share with user |
| GET | /api/documents/:id/collaborators |
List collaborators |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/documents/:id/comments |
List comments |
| POST | /api/documents/:id/comments |
Add comment |
| PATCH | /api/comments/:id |
Update/resolve |
| DELETE | /api/comments/:id |
Delete comment |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/documents/:id/versions |
List versions |
| POST | /api/documents/:id/versions |
Save snapshot |
| POST | /api/documents/:id/versions/:vid/restore |
Restore version |
- users — id, email, display_name, password_hash, avatar_url
- documents — id, title, owner_id, yjs_state (BYTEA), timestamps
- document_versions — id, document_id, yjs_snapshot, content_text, version_num, label
- comments — id, document_id, author_id, anchor (JSONB), content, parent_id, resolved
- collaborators — id, document_id, user_id, permission (view/comment/edit/admin)
make install # Install dependencies
make dev # Start all dev servers
make build # Build all packages
make test # Run tests
make lint # Lint all packages
make typecheck # Type check all packages
make migrate # Run database migrations
make docker-up # Build and start with Docker Compose
make docker-down # Stop Docker services
make docker-clean # Stop and remove volumes
make clean # Remove build artifactsMIT