A full-stack collaborative document editing platform featuring CRDT-based real-time synchronization, AI-powered writing assistance, role-based access control, and production-oriented observability.
- Overview
- Architecture
- Feature Breakdown
- Technology Stack
- Folder Structure
- System Design
- Database Schema
- Role-Based Access Control (RBAC)
- Real-Time Collaboration Workflow
- AI Workflow
- API Overview
- Installation
- Environment Variables
- Local Development Setup
- Deployment Guide
- Monitoring & Observability
- Security
- Testing
- Roadmap
- Screenshots
- License
This project is a full-stack collaborative document editing platform that allows multiple users to edit the same document simultaneously in real time, while leveraging AI-powered writing assistance for grammar correction, tone enhancement, and summarization.
It is inspired by architecture patterns found in products such as Google Docs, Notion, and Coda — conflict-free real-time sync, granular permission models, streaming AI responses, and production-ready observability.
Core capabilities:
- Real-time collaborative editing using Yjs CRDTs over WebSockets
- Rich text editing powered by Quill
- AI-assisted writing using the Google Gemini API, with both standard and SSE-streamed responses
- JWT-based authentication with secure, HTTP-only cookies
- Granular role-based document permissions (Owner / Editor / Viewer)
- Secure document sharing via share links and direct implementation by owner of document
- Centralized logging (Winston, Morgan) and metrics (Prometheus)
- Cloud-native deployment: Vercel (frontend) + Render (backend) + MongoDB Atlas (database)
flowchart TB
subgraph Client["client/src — React + TypeScript"]
Pages[pages/ — Dashboard, Editor, Login, Signup, Share]
Components[components/ui + layouts]
SvcApi[services/api.ts]
SvcSocket[services/socket.ts]
SvcCollab[services/collaboration.ts]
SvcAI[services/ai.ts]
AuthCtx[context/AuthContext.tsx]
end
subgraph Edge["Vercel Edge"]
CDN[Static Asset Delivery]
end
subgraph Backend["server/src — Node.js / Express / TypeScript"]
RouteAuth[routes/auth.ts]
RouteDoc[routes/doc.ts]
RouteAI[routes/ai.ts]
CtrlAuth[controllers/auth.ts]
CtrlDoc[controllers/doc.ts]
CtrlAI[controllers/ai.ts]
SvcDoc[services/document.service.ts]
SvcGemini[services/ai.service.ts]
Mid[middleware/ — auth, rateLimiter, sanitize, validater, error]
ModelLayer[model/ — User.ts, Document.ts]
Socket[socketHandler.ts]
MetricsMw[metrics/metrics.ts + middleware/metricsmiddleware.ts]
Logger[utils/logger.ts]
YjsUtils[utils/yjsUtils.ts]
end
subgraph External["External Services"]
Gemini[Google Gemini API]
Mongo[(MongoDB Atlas)]
end
subgraph Observability["Monitoring"]
Winston[Winston Logs]
MorganLog[Morgan Access Logs]
Prom[Prometheus /metrics]
end
Pages --> SvcApi
Pages --> SvcAI
SvcCollab <--> SvcSocket
SvcSocket <--> Socket
SvcApi -->|REST| RouteAuth
SvcApi -->|REST| RouteDoc
SvcAI -->|REST / SSE| RouteAI
AuthCtx --> SvcApi
CDN --> Pages
RouteAuth --> Mid --> CtrlAuth --> ModelLayer
RouteDoc --> Mid --> CtrlDoc --> SvcDoc --> ModelLayer
RouteAI --> Mid --> CtrlAI --> SvcGemini --> Gemini
Socket --> SvcDoc
Socket --> YjsUtils
ModelLayer --> Mongo
CtrlAuth --> Logger
CtrlDoc --> Logger
RouteAuth --> MetricsMw
RouteDoc --> MetricsMw
RouteAI --> MetricsMw
Logger --> Winston
RouteAuth --> MorganLog
MetricsMw --> Prom
| Layer | Provider | Notes |
|---|---|---|
| Frontend | Vercel | Static React build, edge CDN delivery |
| Backend API + WebSocket | Render | Long-lived process for WS connections |
| Database | MongoDB Atlas | Managed, replicated cluster |
| AI Provider | Google Gemini API | Streaming + standard completions |
| Logs | Winston (app), Morgan (HTTP) | Shipped to stdout / log aggregator |
| Metrics | Prometheus | Scrape endpoint exposed by backend |
- User registration and login
- JWT-based authentication with secure HTTP-only cookies
- Persistent sessions and a "current user" endpoint
- Protected route middleware
- Logout with cookie invalidation
- Create, read, update, and delete documents
- Document sharing via secure share links
- Owner / Editor / Viewer permission model enforced throughout the backend
- Multi-user concurrent editing with conflict-free synchronization (CRDTs, not operational transforms)
- Live presence awareness and connected-user tracking
- Real-time cursor synchronization via the Awareness Protocol
- Room-based collaboration with shared
Y.Docinstances - Automatic merge resolution and memory cleanup for inactive rooms
- React + Quill editor
- Live collaboration bindings (
QuillBinding) - AI-assisted editing actions inline
- Auto-save and manual save
- Grammar correction — grammar, punctuation, clarity
- Text enhancement — readability, tone, structure
- Summarization — concise summaries and key-point extraction
- Streaming generation via Server-Sent Events for progressive, token-by-token rendering
Backend
| Category | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express |
| Language | TypeScript |
| Database | MongoDB + Mongoose |
| Auth | JWT, bcrypt |
| Real-time | WebSocket (ws), Yjs |
| AI | Google Gemini API |
| Logging | Winston, Morgan |
| Metrics | Prometheus client |
Frontend
| Category | Technology |
|---|---|
| Framework | React + TypeScript (Vite) |
| UI Components | shadcn/ui (Button, Dialog, Field, Input, Label, Separator) |
| HTTP Client | Axios (services/api.ts) |
| Editor | Quill |
| Real-time | Yjs, WebSocket client (services/socket.ts, services/collaboration.ts) |
| Routing | React Router (routes/RequireAuth.tsx) |
| Auth State | React Context (context/AuthContext.tsx) |
collab-editor/
├── client/ # React + TypeScript frontend (Vite)
│ ├── src/
│ │ ├── components/
│ │ │ ├── ui/ # shadcn/ui primitives
│ │ │ │ ├── button.tsx
│ │ │ │ ├── deleteshare.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── field.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ └── sharedialogue.tsx
│ │ │ └── New.tsx
│ │ ├── context/
│ │ │ └── AuthContext.tsx
│ │ ├── layouts/
│ │ │ ├── Footer.tsx
│ │ │ └── Navbar.tsx
│ │ ├── lib/
│ │ │ └── utils.ts
│ │ ├── pages/
│ │ │ ├── DashboardPage.tsx
│ │ │ ├── EditorPage.tsx
│ │ │ ├── LoginPage.tsx
│ │ │ ├── SharePage.tsx
│ │ │ └── SignupPage.tsx
│ │ ├── routes/
│ │ │ └── RequireAuth.tsx
│ │ ├── services/
│ │ │ ├── ai.ts
│ │ │ ├── api.ts
│ │ │ ├── collaboration.ts
│ │ │ └── socket.ts
│ │ ├── App.tsx
│ │ ├── main.tsx
│ │ ├── theme.tsx
│ │ └── index.css
│ ├── index.html
│ ├── package.json
│ ├── vite.config.ts
│ ├── vercel.json
│ └── tsconfig.json
│
├── server/ # Node.js + Express + TypeScript backend
│ ├── src/
│ │ ├── config/
│ │ │ ├── db.ts # MongoDB connection
│ │ │ └── env.ts # Environment variable loading
│ │ ├── controllers/
│ │ │ ├── ai.ts
│ │ │ ├── auth.ts
│ │ │ └── doc.ts
│ │ ├── metrics/
│ │ │ └── metrics.ts # Prometheus metric definitions
│ │ ├── middleware/
│ │ │ ├── ai.ts # AI-route-specific guards
│ │ │ ├── auth.ts # JWT verification
│ │ │ ├── error.ts # Centralized error handler
│ │ │ ├── metricsmiddleware.ts # Request metrics collection
│ │ │ ├── notFound.ts
│ │ │ ├── rateLimiter.ts
│ │ │ ├── sanitize.ts
│ │ │ └── validater.ts
│ │ ├── model/
│ │ │ ├── Document.ts
│ │ │ └── User.ts
│ │ ├── routes/
│ │ │ ├── ai.ts
│ │ │ ├── auth.ts
│ │ │ └── doc.ts
│ │ ├── services/
│ │ │ ├── ai.service.ts
│ │ │ └── document.service.ts
│ │ ├── utils/
│ │ │ ├── logger.ts # Winston logger
│ │ │ └── yjsUtils.ts # Yjs encode/decode helpers
│ │ ├── app.ts # Express app setup
│ │ ├── index.ts # Server entrypoint
│ │ └── socketHandler.ts # WebSocket / Yjs collaboration logic
│ ├── tests/
│ │ ├── integration/
│ │ │ └── api.test.ts
│ │ └── unit/
│ │ └── permission.test.ts
│ ├── logs/
│ │ ├── combined.log
│ │ └── error.log
│ ├── jest.config.cjs
│ ├── package.json
│ └── tsconfig.json
│
├── .gitignore
└── README.md
The backend follows a layered MVC-style structure rather than a feature-module structure — each concern (auth, document, AI) is split horizontally across routes/, controllers/, services/, and model/:
| Layer | Files | Responsibility |
|---|---|---|
| Routes | routes/auth.ts, routes/doc.ts, routes/ai.ts |
Express endpoint definitions, wiring middleware to controllers |
| Controllers | controllers/auth.ts, controllers/doc.ts, controllers/ai.ts |
Request/response handling, input parsing, calling services |
| Services | services/document.service.ts, services/ai.service.ts |
Business logic: permission evaluation, Gemini calls, document operations |
| Models | model/User.ts, model/Document.ts |
Mongoose schemas and instance methods (e.g., password hashing) |
| Middleware | middleware/auth.ts, rateLimiter.ts, sanitize.ts, validater.ts, ai.ts, error.ts, notFound.ts |
JWT verification, rate limiting, input sanitization/validation, AI-route guards, centralized error handling |
| Collaboration | socketHandler.ts, utils/yjsUtils.ts |
WebSocket connection handling, Y.Doc room management, CRDT encode/decode |
| Observability | utils/logger.ts, metrics/metrics.ts, middleware/metricsmiddleware.ts |
Winston logging, Prometheus metric definitions, per-request metrics capture |
| Config | config/db.ts, config/env.ts |
MongoDB connection setup, environment variable loading |
This separation keeps request handling, business logic, and persistence independently testable, even though they aren't grouped into per-feature folders.
erDiagram
USER {
ObjectId _id
string username
string email
string password
date createdAt
date updatedAt
}
DOCUMENT {
ObjectId _id
string title
string content
ObjectId owner
binary yjsState
string shareLink
boolean isPublic
date lastSaved
date createdAt
date updatedAt
}
PERMISSION {
ObjectId user
string role
}
USER ||--o{ DOCUMENT : owns
DOCUMENT ||--o{ PERMISSION : "has many"
USER ||--o{ PERMISSION : "is assigned"
User Collection
| Field | Type | Notes |
|---|---|---|
username |
String | Unique |
email |
String | Unique, indexed |
password |
String | bcrypt-hashed |
createdAt / updatedAt |
Date | Timestamps |
The user model exposes a password-comparison method built on bcrypt for secure authentication.
Document Collection
| Field | Type | Notes |
|---|---|---|
title |
String | |
content |
String | Serialized rich-text snapshot |
owner |
ObjectId | References User |
permissions |
[{ user, role }] |
Embedded permission list |
shareLink |
String | Unique token for link-based access |
isPublic |
Boolean | Whether the share link grants access without invitation |
yjsState |
Binary | Persisted CRDT state for recovery |
lastSaved |
Date | Last successful auto-save |
createdAt / updatedAt |
Date | Timestamps |
Every document maintains a permissions array of { user, role } entries. Roles follow a strict hierarchy:
Owner > Editor > Viewer
Owner inherits all Editor and Viewer permissions; Editor inherits Viewer permissions. The Document model exposes a permission-evaluation utility that resolves this hierarchy for any access-control check.
| Capability | Owner | Editor | Viewer |
|---|---|---|---|
| View document | ✅ | ✅ | ✅ |
| Edit content | ✅ | ✅ | ❌ |
| Real-time collaboration | ✅ | ✅ | ✅ (read-only session) |
| Use AI features | ✅ | ✅ | ❌ |
| Delete document | ✅ | ❌ | ❌ |
| Generate / manage share links | ✅ | ❌ | ❌ |
| Invite / remove collaborators | ✅ | Only if explicitly granted | ❌ |
| Change collaborator roles | ✅ | ❌ | ❌ |
| Transfer ownership | ✅ (future-ready) | ❌ | ❌ |
Authorized users (owners, and editors with explicit grants) can:
- Invite collaborators and assign a role at invitation time
- Upgrade or downgrade an existing collaborator's role
- Revoke a collaborator's access entirely
- Generate and rotate secure share links
When access is revoked, enforcement is immediate and consistent across the system:
- Subsequent REST API requests for that document are denied
- The user's real-time collaboration session is terminated
- Protected document routes return an authorization error
- AI actions on the document are blocked
Permission checks are not centralized in a single place — they are validated at every layer that touches a document:
flowchart LR
Req[Incoming Request] --> AuthCheck[Authentication Check]
AuthCheck --> RoleCheck{Permission Evaluation Utility}
RoleCheck -->|API Layer| API[REST CRUD / Sharing Routes]
RoleCheck -->|Collaboration Layer| Room[WebSocket Room Join]
RoleCheck -->|AI Layer| AIGate[AI Action Gate]
API -->|Authorized| Allow1[Proceed]
Room -->|Authorized| Allow2[Join Y.Doc Room]
AIGate -->|Authorized| Allow3[Invoke Gemini]
API -->|Unauthorized| Deny1[403 Forbidden]
Room -->|Unauthorized| Deny2[Connection Rejected]
AIGate -->|Unauthorized| Deny3[403 Forbidden]
- API layer — every protected document route validates authentication, document ownership, and required role level.
- Collaboration layer — before a client is allowed to join a collaboration room, identity is verified and document permissions are re-validated; unauthorized users are denied a WebSocket connection.
- AI layer — AI actions require valid authentication and sufficient document permission; users without access cannot invoke AI operations on a protected document.
This mirrors the layered permission enforcement found in systems like Google Docs, Notion, and Confluence.
Synchronization is handled entirely through CRDT state merging — there is no operational-transform layer and no central "lock" on document state.
sequenceDiagram
participant U1 as User A (Quill)
participant Y1 as Y.Doc (Client A)
participant WS as WebSocket Server
participant Y2 as Y.Doc (Client B)
participant U2 as User B (Quill)
U1->>Y1: Local edit
Y1->>Y1: Update Y.Text
Y1->>WS: Broadcast CRDT update
WS->>Y2: Relay update to peers in room
Y2->>Y2: Merge update (conflict-free)
Y2->>U2: Re-render editor
Note over Y1,Y2: No manual conflict resolution required
Editing flow
- A user edits text in the Quill editor.
QuillBindingpropagates the change into the localY.Text.- The
Y.Docstate updates. - The WebSocket provider broadcasts the encoded update.
- The backend relays the update to all peers connected to that document's room.
- Peers merge the update automatically via CRDT semantics.
- The UI updates instantly for every connected client, with no manual conflict resolution.
Persistence flow
- Users edit the document; CRDT state synchronizes instantly across clients.
- An auto-save interval periodically serializes the current state.
- Content (and the binary
yjsState) is persisted to MongoDB. - MongoDB acts as durable backup storage, while live collaboration state remains owned by Yjs in memory.
sequenceDiagram
participant User
participant Frontend
participant Backend as AI Module
participant Gemini as Google Gemini API
User->>Frontend: Select text + trigger AI action
Frontend->>Backend: POST /api/ai/:action (SSE)
Backend->>Backend: Validate auth + document permission
Backend->>Backend: Apply rate limit
Backend->>Gemini: Generate request
Gemini-->>Backend: Streamed tokens
Backend-->>Frontend: SSE chunks
Frontend-->>User: Progressive render
User->>Frontend: Apply result to editor
- The user selects text and triggers an AI action (grammar fix, enhancement, or summarization).
- The backend validates the user's permission on the target document.
- Rate limits are enforced on the AI endpoint specifically.
- The Gemini API generates the response.
- For streaming requests, output is sent as Server-Sent Events, chunk by chunk.
- The frontend renders the content progressively as it arrives.
- The user reviews and applies the generated result back into the Quill editor.
Base URL:
/api
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/register |
Create a new user account |
POST |
/auth/login |
Authenticate and receive a session cookie |
POST |
/auth/logout |
Invalidate the current session |
GET |
/auth/me |
Return the current authenticated user |
| Method | Endpoint | Description |
|---|---|---|
GET |
/documents |
List documents owned by or shared with the user |
POST |
/documents |
Create a new document |
GET |
/documents/:id |
Retrieve a document (permission-checked) |
PUT |
/documents/:id |
Update document content or metadata |
DELETE |
/documents/:id |
Delete a document (owner only) |
POST |
/documents/:id/share |
Generate or update a share link |
GET |
/documents/shared/:token |
Access a document via share link |
POST |
/documents/:id/collaborators |
Invite a collaborator with a role |
PATCH |
/documents/:id/collaborators/:userId |
Update a collaborator's role |
DELETE |
/documents/:id/collaborators/:userId |
Revoke a collaborator's access |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ai/grammar |
Grammar and clarity correction |
POST |
/ai/enhance |
Tone and readability enhancement |
POST |
/ai/summarize |
Summarization / key-point extraction |
POST |
/ai/stream/:action |
SSE-streamed variant of the above actions |
| Protocol | Endpoint | Description |
|---|---|---|
WS |
/ws/documents/:id |
Join a document's collaboration room (Yjs sync + awareness) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/metrics |
Prometheus-formatted metrics |
GET |
/health |
Liveness/readiness probe |
- Node.js 18+
- npm or yarn
- A MongoDB instance (local or Atlas)
- A Google Gemini API key
git clone https://github.com/your-username/collab-editor.git
cd collab-editor# Server
cd server
npm install
# Client
cd ../client
npm installCreate a .env file in server/ (see .env.example):
# Server
PORT=5000
NODE_ENV=development
# Database
MONGODB_URI=mongodb+srv://<user>:<password>@cluster.mongodb.net/collab-editor
# Auth
JWT_SECRET=your_jwt_secret
JWT_EXPIRES_IN=7d
COOKIE_SECURE=false
# AI
GEMINI_API_KEY=your_gemini_api_key
GEMINI_MODEL=gemini-1.5-pro
# CORS
CLIENT_ORIGIN=http://localhost:3000
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
AI_RATE_LIMIT_MAX=20Create a .env file in client/:
VITE_API_BASE_URL=http://localhost:5000/api
VITE_WS_URL=ws://localhost:5000# Terminal 1 — server
cd server
npm run dev
# Terminal 2 — client
cd client
npm run devBy default:
- Backend runs on
http://localhost:5000 - Frontend (Vite) runs on
http://localhost:5173 - WebSocket collaboration server runs on the same port as the backend, under
/ws
- Create a new Web Service on Render, pointing at the
server/directory. - Set the build command to
npm install && npm run buildand the start command tonpm start. - Configure all backend environment variables in the Render dashboard.
- Ensure the service plan supports long-lived WebSocket connections.
- Import the repository into Vercel and set the root directory to
client/. - Set
VITE_API_BASE_URLandVITE_WS_URLto your deployed Render backend's HTTPS/WSS URLs. - Deploy — Vercel will build and serve the static frontend via its edge CDN.
- Provision a cluster on MongoDB Atlas.
- Whitelist the Render service's outbound IP (or allow access from anywhere for simplicity, scoped down for production).
- Use the generated connection string as
MONGODB_URI.
Used for application events, database connection lifecycle, error tracking, and service startup/shutdown logs, categorized into info, warn, error, and debug levels.
Tracks HTTP requests, response status codes, response times, and request metadata — useful for API debugging and traffic analysis.
Exposes an operational metrics endpoint (/metrics) including:
- Total request counts
- Response duration histograms
- Active WebSocket connection counts
- Error counts by route
- Per-endpoint performance breakdowns
These metrics are scrape-compatible with a standard Prometheus + Grafana stack for dashboards and alerting.
| Layer | Measures |
|---|---|
| Authentication | JWT validation, secure HTTP-only cookies, bcrypt password hashing |
| API Security | Helmet security headers, CORS allowlists, input validation, request sanitization |
| Abuse Prevention | Global rate limiting, route-specific rate limiting, dedicated AI endpoint throttling |
| Data Protection | Permission validation, resource ownership checks, protected document access enforcement |
Permission and ownership checks are applied consistently across REST APIs, document CRUD operations, share-link workflows, real-time collaboration entry points, and AI-assisted actions — not just at the outermost route layer.
- User model (password hashing, comparison)
- Authentication utilities
- Permission validation logic
- Auth endpoints (register, login, session)
- Document endpoints (CRUD)
- Permission boundary enforcement
- End-to-end share-link workflows
- Cross-role authorization checks (owner/editor/viewer)
cd server
npm run test
npm run test:integration- Ownership transfer workflow (already permission-ready in the data model)
- Offline editing with local persistence and reconnection sync
- Document version history and rollback
- Comment threads and inline suggestions
- Org/workspace-level access control
- Pluggable AI providers beyond Gemini
- Mobile-responsive editor experience
Add screenshots or screen recordings here.
| Dashboard | Editor | AI Panel |
|---|---|---|
./docs/screenshots/dashboard.png |
./docs/screenshots/editor.png |
./docs/screenshots/ai-panel.png |
This project is licensed under the MIT License.