← Back to projects

Real-Time Chat System

TypeScriptNestJSGraphQLReactRedisPostgreSQLDocker

🔗 View on GitHub

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 up brings up the full stack with 2 replicas per service

Architecture

Architecture diagram

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.