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.

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
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.
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.
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
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
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.
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.
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.
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
INTERACTIVE INFRASTRUCTURE DIAGRAM - DRAG · ZOOM · PAN · HOVER FOR DETAILS
TRADE-OFFS
Chose Redis over in-memory state for presence to support horizontal scaling
Used modular monolith instead of microservices to reduce deployment complexity
Implemented cursor pagination to avoid offset inefficiencies at scale
Chose VPS + Docker over managed services to gain full control over infrastructure and deployment behavior
Implemented blue-green deployment to eliminate downtime at the cost of added system complexity
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
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.
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.
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.
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.
