CASE STUDY // 02

Real-Time Chat Platform

A real-time messaging system engineered for consistency, presence accuracy, and scalable data access - built to handle the edge cases most chat apps ignore.

Socket.IORedisMongoDBReactNode.jsExpressClerkAWS S3
Real-Time Chat Platform screenshot 1 of 6
1 / 6

THE CHALLENGE

Reliable Messaging at Concurrent Scale

The core challenge wasn't just sending messages - it was guaranteeing ordering, delivery state consistency, and presence accuracy across multiple browser tabs and reconnection scenarios. Webhook signature verification required raw body preservation before Express middleware parsing. Atomic consistency between message creation and conversation metadata updates demanded careful transaction orchestration. Meanwhile, rate limiting had to be distributed across potential server instances without falling back to fragile in-memory stores.

ENGINEERING RESPONSE

Modular Monolith with Real-Time Backbone

wifi

Socket.IO with Redis Presence

Enabled real-time messaging with Socket.IO, backed by Redis presence tracking using atomic INCR/DECR counters and TTL heartbeats - ensuring accurate multi-tab online/offline state without false disconnects.

view_list

Cursor-Based Pagination

Replaced offset-based queries with opaque base64-encoded cursors keyed on (createdAt, _id), delivering O(1) pagination performance and preventing duplicate rendering during concurrent real-time inserts.

hub

SOLID Modular Architecture

Decomposed the backend into 7 domain modules (Users, Conversations, Messages, Chat-Requests, Presence, Uploads, Webhooks) - each with its own model, repository, service, controller, and validator layers following interface-driven dependency injection.

INTERNAL ARCHITECTURE

Modular Monolith - Layered Service ArchitectureSingle Deployable Unit

API LAYER
APPLICATION LAYER
DOMAIN LAYER
DATA ACCESS LAYER
CROSS-CUTTING
Primary Flow
React Flow mini map

INTERACTIVE ARCHITECTURE DIAGRAM - DRAG · ZOOM · PAN · HOVER FOR DETAILS

Chose a modular monolith over microservices to maintain deployment simplicity while preserving clear domain boundaries and scalability.

DEEP DIVE

bolt

Why Redis for Presence?

Presence tracking requires sub-second latency and atomic updates - Redis INCR/DECR with TTL enabled accurate multi-session tracking without race conditions.

grid_view

Cursor Pagination Internals

Each cursor encodes a base64 JSON of { createdAt, _id }. The query filters using a compound condition (createdAt < cursor OR createdAt = cursor AND _id < cursor._id) against a compound descending index, yielding stable reverse-chronological traversal immune to mid-scroll inserts.

cloud

S3 Upload State Machine

File uploads follow a temp-to-final S3 flow: presigned PUT URL → upload to temp/ prefix → on message send, backend atomically copies to final path and deletes temp. A 24-hour S3 lifecycle rule auto-cleans orphaned temp files, preventing storage leaks.

sync

Zero-Downtime Deployment Strategy

Implemented blue-green deployment with health checks and traffic switching using Nginx to ensure seamless releases without downtime.

THE HARD PARTS

Navigating Real-Time Friction

Solving Multi-Tab Presence Consistency

Each browser tab opens its own socket connection, so a naive approach would toggle users offline every time a single tab closes. Solved with Redis INCR/DECR counters per userId - only emitting 'user:offline' when the count drops to zero, paired with a 120s TTL heartbeat as a safety net.

Atomic Message + Conversation Updates

Creating a message and updating the conversation's lastMessage field must succeed or fail together. Implemented a withTransaction() helper wrapping MongoDB sessions, ensuring atomic rollback on any failure - critical for the chat-request accept flow which creates a conversation and updates request status in one transaction.

Webhook Body Parsing Order

Clerk webhooks require the raw request body for Svix HMAC signature verification, but Express.json() consumes it. Solved by registering webhook routes with express.raw() before any body-parsing middleware in the middleware chain.

7

DOMAIN MODULES

120s

PRESENCE TTL

O(1)

PAGINATION COST

3-Stage

DELIVERY TRACKING

INFRASTRUCTURE & DEPLOYMENT

Production-Grade Deployment & Reliability

Not all of this infrastructure was demanded by the app's actual load. The two groups below separate what the challenges on this page directly required from what was built as deliberate operational practice.

Core to the Problem

Dockerized Multi-Service Architecture

Containerized backend services using Docker, isolating API and reverse proxy layers while ensuring consistent environments across development and production. Redis and MongoDB run as containerized sidecars, directly supporting the presence counter and transaction features the challenges demanded.

Nginx Reverse Proxy with SSL

Configured Nginx with Let's Encrypt for HTTPS, handling TLS termination and routing. WebSocket connections over wss:// require a correctly configured reverse proxy — this is not optional for Socket.IO in production.

CI/CD with GitHub Actions

Implemented automated deployments via GitHub Actions using SSH workflows to pull, build, and deploy containers without manual intervention. Ensures presence and pagination fixes reach production reliably without human error in the deploy step.

Built as Deliberate Ops Practice

Zero-Downtime Blue-Green Deployment

Designed blue-green deployment where new containers are started, health-checked, and switched into traffic only after validation. This app's traffic doesn't justify the complexity — it was built as practice for zero-downtime release patterns.

Automatic Failover with Nginx Load Balancing

Configured Nginx upstream failover across multiple containers with retry logic to ensure uninterrupted service. Intentionally over-engineered for a single-node app; the goal was gaining hands-on experience with upstream failover configuration.

Self-Healing Infrastructure

Implemented a watchdog system using systemd to monitor container health and automatically restart unhealthy services. Added as an ops discipline exercise, not because the app had a crash-rate problem.

Auto-Recovery on Server Restart

Built systemd-based recovery to automatically restart Docker services and reload Nginx after server reboot. Useful in any production context; included here to practice the full systemd service lifecycle.

SYSTEM ARCHITECTURE

Real-Time Chat InfrastructureZero-Downtime + Failover

INFRASTRUCTURE
APPLICATION
DATA LAYER
EXTERNAL SERVICES
CI/CD
Primary Flow
React Flow mini map

INTERACTIVE INFRASTRUCTURE DIAGRAM - DRAG · ZOOM · PAN · HOVER FOR DETAILS

TRADE-OFFS

swap_horiz

Chose Redis over in-memory state for presence to support horizontal scaling

swap_horiz

Used modular monolith instead of microservices to reduce deployment complexity

swap_horiz

Implemented cursor pagination to avoid offset inefficiencies at scale

swap_horiz

Chose VPS + Docker over managed services to gain full control over infrastructure and deployment behavior

swap_horiz

Implemented blue-green deployment to eliminate downtime at the cost of added system complexity

swap_horiz

Used Nginx load balancing instead of external load balancers to keep the system lightweight and cost-efficient

KNOWN LIMITATIONS

What This Implementation Doesn't Cover

warning

No API-Level Rate Limiting

Redis was chosen partly to support distributed rate limiting, but no rate-limiting middleware is applied to message sends or API endpoints. A single connected socket can flood the system — the infrastructure is ready for it, but the guard isn't wired in.

warning

Presence State Is Not Durable

Presence counters live exclusively in Redis with no persistence configured. A Redis restart clears all INCR/DECR state and every connected user appears offline until their next heartbeat re-emits the counter — recovery depends entirely on live sockets, not on a stored snapshot.

warning

Orphaned Final-Path S3 Files on Failed Message Commits

The temp-to-final S3 copy runs before the MongoDB message write is committed. If the MongoDB write fails after the copy, the final-path file sits in S3 with no message referencing it. The 24-hour lifecycle rule covers the temp prefix only — orphaned final-path files are not auto-cleaned.

warning

Cursor Integrity Is Not Validated Server-Side

Pagination cursors are opaque base64-encoded JSON, but the server does not verify the signature or structure of an incoming cursor. A malformed or tampered cursor produces unexpected query behavior — empty results or a parse error — rather than a clean 400 response.

RETROSPECTIVE

Design for Disconnection, Build for Consistency.

The biggest lesson was that real-time systems are fundamentally about handling the unhappy path - socket disconnects, stale presence state, partial transaction failures, and orphaned uploads. Every feature demanded thinking in terms of 'what happens when this fails mid-way?' rather than just the success flow. Building the cursor pagination and presence counter systems taught me that elegant distributed design often comes down to choosing the right data structure at the right layer.