A real-time collaborative text editor built with ProseMirror/TipTap and a backend API for operational transformation-based collaboration.
cadmus-editor/
├── client/ # React + TypeScript + TipTap editor
│ └── README.md # Detailed client implementation docs
└── server/ # Backend API server (TypeScript/Node.js)
└── PERSISTENCE_SETUP.md # Persistence testing guide
- Rich Text Editor: Full-featured text editor using TipTap (ProseMirror-based)
- Live Word Counter: Real-time word count display
- Real-time Collaboration: Multiple users can edit the same document simultaneously
- Persistence: Document state and steps are saved across server restarts
- Node.js (v16+ minimum, v18+ recommended)
- npm or yarn
cd client
npm install
npm run devThe client will run on http://localhost:3000
cd server
npm install
npm run devThe server will run on http://localhost:4000
- Built with React + TypeScript
- Uses TipTap editor framework (ProseMirror-based)
- Vite for development and building
- Node.js + TypeScript
- HTTP API for collaboration (polling-based)
- File-based persistence
- Modular architecture (DAO/Controller separation)
- Feature-based modules structure
- ESLint + Prettier for both client and server
- Husky with lint-staged for pre-commit hooks
- TipTap editor with rich text capabilities
- Live word counter
- ProseMirror collab plugin integration
- Backend API endpoints for collaboration
- Step synchronization between clients
- Document state catch-up on reload
- Debounced sending of steps (300ms delay)
- Step storage across server restarts (file-based:
server/data/state.json) - Ordered step validation
- Automatic state recovery on server startup
GET /api/events?version={n}- Get steps since version nPOST /api/events- Submit new steps (body: {version, steps, clientID})GET /api/version- Get current versionGET /health- Health check
- File-based persistence (
server/data/state.json) - Steps survive server restarts
- Single server architecture
For horizontal scaling with multiple servers:
I used simple file storage for the prototype since performance wasn't a priority here. But in production, writing every step to disk would create bottlenecks and fail with multiple concurrent editors. Plus, with horizontal scaling, each server would have its own file copy, leading to inconsistent document states. I'd use PostgreSQL since it provides ACID transactions and strict ordering essential for collaborative editing, while NoSQL databases have eventual consistency issues that can break step ordering. With proper indexing on document ID and version, you get reliable step storage and fast history lookups. For high-traffic documents, adding Redis to cache recent steps would help. The main scaling challenge is ensuring steps are processed in order across multiple servers: you'd need coordination or database constraints to reject out-of-order steps.
I chose HTTP polling following ProseMirror's collab documentation approach: it's simpler and avoids reconnection complexity. The 1-second polling works well for collaborative editing. WebSockets can be added later for instant updates and reduced server load with many concurrent users.
Used ProseMirror's collab plugin (operational transformation) as per requirements instead of TipTap's Yjs collaboration.
This project implements collaborative editing using ProseMirror's operational transformation algorithm. The backend focuses on ordered storage and replay of steps without recreating ProseMirror state on the server.