A real-time collaborative text editor built with FastAPI, WebSockets, and Redis, allowing multiple users to edit the same document simultaneously while staying synchronized with live presence and typing indicators.
- Real-time collaborative editing
- Live user presence
- Typing indicators
- Automatic synchronization of document changes
- Redis-powered pub/sub for low-latency updates
- WebSocket-based communication
- Real-time event broadcasting: Events are published to all users in a room and requires no polling.
- Pub/Sub Systems: A redis pub/sub channel is used as the medium for event publishing. This solves the multi-instance limitations by ensuring all users in a room have access to events irrespective of the instance connected to.
- Presence and Typing indicators: Broadcast presence and typing events in real-time.
- Event Ordering: Operation events are assigned a number to maintain sequential order.
- Operational Transformation: Applied to preserve all modifications from conflicting operations by merging writes from multiple users.
Before running Notix locally, make sure you have:
- Python 3.12 or newer
- Docker and Docker Compose
- uv (recommended for dependency management)
- access to a PostgreSQL instance, Redis, RabbitMQ, and Resend credentials
The repository includes a full local stack for the API, worker, PostgreSQL, Redis, and RabbitMQ.
docker compose up --buildThis will launch:
- PostgreSQL for app data
- PostgreSQL for test data
- Redis
- RabbitMQ management UI at http://localhost:15672
- API server at http://localhost:8000
- Celery worker
docker compose downgit clone https://github.com/<your-username>/cowrite.git
cd cowriteuv synccp .env.example .envuv run alembic upgrade headuv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Postman can be used to test the WebSocket endpoint.
- Open Postman.
- Create a new WebSocket Request.
- Connect to the application's WebSocket URL.
- Once connected, clients exchange JSON messages over the WebSocket.
Example:
ws://localhost:8000/api/v1/ws/?token={bearer_token}
After connecting:
- Send a ping message every 10 seconds to remain connected.
- Send typing events while testing.
- Send document editing events to verify synchronization.
- Observe broadcasts from other connected clients.
Every connected client must send a ping event every 10 seconds. The server refreshes the user's Redis presence key whenever a ping is received.
If the client stops sending pings:
- The Redis presence key expires.
- The user is considered offline.
- The server removes the user from the room.
Example:
{ "event": "ping", "doc_id": "abc" }
It is recommended to implement this as a repeating timer on the client that sends a ping every 10 seconds for the lifetime of the connection.
Cowrite uses typing events to provide live typing indicators. Every keystroke should send a typing event.
Example:
{ "event": "typing", "doc_id": "abc" }
Clients do not need to manually send a typing stopped event. When no typing event has been received for 3 seconds, the server automatically broadcasts that the user has stopped typing.
Run the test suite with:
uv run pytestRun in verbose mode:
uv run pytest -v