Real-Time Chat System
Overview
A scalable real-time chat application built as a monorepo with two NestJS/GraphQL backend services (User Service and Chat Service) behind a React/Apollo Client frontend. The entire stack — including PostgreSQL, Redis, and multiple service replicas — is orchestrated with Docker Compose.
Features
- Real-time messaging — Live message delivery via GraphQL subscriptions over WebSocket
- Multi-instance fan-out — Redis Pub/Sub ensures messages reach all connected clients regardless of which service replica they're connected to
- Room management — Create, join, and manage chat rooms with membership controls
- Role-based permissions — Owner/Admin/Member hierarchy with granular access control
- Authentication — JWT-based auth with registration, login, password reset, and account deletion
- Guaranteed message ordering — Atomic sequence numbers via PostgreSQL row-level locking (
SELECT … FOR UPDATE) - Dockerized deployment — Single
docker-compose upbrings up the full stack with 2 replicas per service
Architecture
The system is composed of five core components:
- User Service (×2 replicas) — Handles registration, JWT issuance, profile management, and account lifecycle
- Chat Service (×2 replicas) — Manages rooms, memberships, roles, message persistence, and real-time delivery
- Frontend — React SPA served by nginx, which reverse-proxies API requests to the backend services
- PostgreSQL — Separate logical databases for each service, provides row-level locking for sequence number assignment
- Redis — Pub/Sub broker for cross-instance message fan-out, plus token denylist and rate-limit counters
Technical Highlights
The most interesting engineering challenge was guaranteeing strict message ordering across multiple service instances. Each room has a dedicated row in a room_sequences table. When a message is sent, the service acquires an exclusive row-level lock, increments the counter, inserts the message with the assigned sequence number, and commits — all in a single transaction. This serializes concurrent writes within a room and guarantees monotonically increasing sequence numbers with no gaps.
For cross-instance delivery, after a message is persisted, the service publishes a BrokerMessageEvent to a Redis Pub/Sub channel scoped to the room. Every Chat Service replica subscribes to channels for rooms with active WebSocket clients, so a message sent to Instance 1 is immediately forwarded to clients connected to Instance 2.
What I Learned
This project deepened my understanding of distributed systems trade-offs. Building the sequence number mechanism taught me about PostgreSQL's MVCC and row-level locking semantics. Working with Redis Pub/Sub revealed the challenges of ephemeral messaging — handling broker disconnections, the absence of backpressure, and ensuring clients can recover missed messages via history queries. Orchestrating multi-replica services with Docker Compose gave me practical experience with service discovery, health checks, and container networking.