Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
13 Sep 2025 · 6 min read ·Article 97 / 110
Go

97. Case Study: A Real-Time Chat Service

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

97. Case Study: A Real-Time Chat Service

As an engineer, the challenge of building a real-time chat service is always an interesting one to explore. Delivering low-latency two-way communication along with scalability and adequate security is a complex combination, yet a deeply fundamental one in today’s digital era. In this article, I’ll break down a case study on building a real-time chat service with WebSocket using Node.js and React, complete with code samples, simulations, and illustrations of the data flow. I hope you find it useful!


1. Background: Why Real-Time Chat?

These days, real-time chat services are everywhere: from messaging apps and customer support to live comment features on event streaming platforms. Real-time solutions have become a key driver of productivity and user engagement. In this case, we want to build a simple chat service, a group chat, with the following features:

  • Each user can join one or more rooms
  • Messages are sent and received in real time
  • Chat history is stored
  • Handling user join/leave notifications

2. Architecture in Brief

The architecture we’ll use is as follows:

  • Client: React (WebSocket client)
  • Server: Node.js with Express & ws (WebSocket library)
  • Database: MongoDB (storage for chat history & user-room relations)

Architecture diagram:

MERMAID
flowchart LR
    User1 --Socket--> Server
    User2 --Socket--> Server
    Server --REST(GET/POST)--> MongoDB
    Server --Socket Broadcast--> User1
    Server --Socket Broadcast--> User2

The server acts as a two-way “relay”: it handles socket connections from clients, forwards messages to other users in the same room, and persists chats to the database.

3. Data Schema Design

Here is an example MongoDB schema for the chat collection:

FieldTypeDescription
_idObjectIdPrimary key
room_idStringRoom ID
senderStringUser who sent the message
messageStringMessage content
created_atISODateTimestamp when sent

And the user-to-room relation (using the user_rooms collection):

FieldTypeDescription
_idObjectIdPrimary key
user_idStringUser ID
room_idStringRoom ID

4. Backend Implementation with Node.js

Let’s start with the WebSocket server.

js
 1// index.js
 2const express = require('express');
 3const http = require('http');
 4const WebSocket = require('ws');
 5const { MongoClient } = require('mongodb');
 6
 7const app = express();
 8const server = http.createServer(app);
 9const wss = new WebSocket.Server({ server });
10
11// MongoDB connection
12const mongoClient = new MongoClient('mongodb://localhost:27017');
13let chatCollection;
14mongoClient.connect().then(client => {
15    const db = client.db('realtime_chat_demo');
16    chatCollection = db.collection('chats');
17});
18
19// Map user to ws
20const rooms = {};
21
22wss.on('connection', (ws) => {
23    ws.on('message', async (data) => {
24        const msg = JSON.parse(data);
25        if (msg.type === 'JOIN') {
26            ws.room = msg.room_id;
27            // Register socket to the room
28            rooms[ws.room] = rooms[ws.room] || [];
29            rooms[ws.room].push(ws);
30            ws.send(JSON.stringify({ type: 'NOTIF', text: `Welcome to room ${ws.room}` }));
31        }
32        else if (msg.type === 'CHAT') {
33            // Save to MongoDB
34            await chatCollection.insertOne({
35                room_id: ws.room,
36                sender: msg.sender,
37                message: msg.message,
38                created_at: new Date()
39            });
40            // Broadcast to all ws in that room
41            (rooms[ws.room] || []).forEach(client => {
42                if (client.readyState === WebSocket.OPEN) {
43                    client.send(JSON.stringify({
44                        type: 'CHAT',
45                        sender: msg.sender,
46                        message: msg.message,
47                        room_id: ws.room
48                    }));
49                }
50            });
51        }
52    });
53    ws.on('close', () => {
54        if (ws.room && rooms[ws.room]) {
55            rooms[ws.room] = rooms[ws.room].filter(client => client !== ws);
56        }
57    });
58});
59
60server.listen(9000, () => {
61    console.log('Listening on port 9000');
62});

Quick Explanation

  • When a client sends a JOIN type, the server adds the socket to a specific room.
  • For a CHAT message, the server saves it to the database and forwards the message to all sockets in that room.
  • The broadcast is done locally in the server’s memory (suitable for small to medium scale).

5. Client Side: React + WebSocket

A React component to connect and chat in real time.

jsx
 1// ChatRoom.jsx
 2import React, { useState, useEffect, useRef } from 'react';
 3
 4const WS_URL = 'ws://localhost:9000';
 5
 6export default function ChatRoom({ user, room_id }) {
 7    const [messages, setMessages] = useState([]);
 8    const [input, setInput] = useState('');
 9    const ws = useRef(null);
10
11    useEffect(() => {
12        ws.current = new WebSocket(WS_URL);
13        ws.current.onopen = () => {
14            ws.current.send(JSON.stringify({ type: 'JOIN', room_id }));
15        };
16        ws.current.onmessage = (e) => {
17            const msg = JSON.parse(e.data);
18            if (msg.type === 'CHAT' || msg.type === 'NOTIF') {
19                setMessages((prev) => [...prev, msg]);
20            }
21        };
22        return () => ws.current.close();
23    }, [room_id]);
24
25    const sendMessage = () => {
26        ws.current.send(JSON.stringify({
27            type: 'CHAT',
28            sender: user,
29            message: input
30        }));
31        setInput('');
32    };
33
34    return (
35        <div>
36            <ul>
37                {messages.map((msg, i) =>
38                    <li key={i}><b>{msg.sender || 'System'}</b>: {msg.message || msg.text}</li>
39                )}
40            </ul>
41            <input value={input} onChange={e => setInput(e.target.value)} />
42            <button onClick={sendMessage}>Send</button>
43        </div>
44    );
45}

Usage Simulation

  1. Run the server.
  2. Open two browser tabs and join the same room_id, for example “room1”.
  3. Type and send a message; it will appear in both tabs in real time.

6. Handling Scale: Broadcast and Distribution

The initial system above is sufficient for tens to hundreds of active users. However, at the scale of thousands of connections, memory and the socket broadcast process must be distributed.

The principle is:

  • If there is more than one WebSocket server, relay messages using a message broker (for example, Redis Pub/Sub).
  • Message delivery between server nodes is done synchronously, so that users in a room spread across different servers still receive messages in real time.

Flow diagram with Redis as pub/sub:

MERMAID
sequenceDiagram
    participant UserA as Client A
    participant Node1 as WebSocket Node 1
    participant Node2 as WebSocket Node 2
    participant Redis as Redis Pub/Sub

    UserA->>Node1: Send message (room1)
    Node1->>Redis: Publish (room1, message)
    Redis->>Node2: Subscribe (room1, message)
    Node2->>UserB: Forward message

With an architecture like this, each server only needs to broadcast to the clients connected to it, while the data across nodes stays in sync.


7. Pros & Cons Table for the Simple WebSocket Solution

AspectStrengthsWeaknesses
Real-timeVery low latencyPotential bottleneck on a single node
SimplicityEasy to implement, fast developmentNeeds a redesign for high scale
ScalabilityEasy to scale horizontally with pub/subAdditional message broker infra
Store Chat HistoryFree to integrate a NoSQL DBNeeds limits on DB growth
SecurityEasy to add authentication/authorization via the ws handshakeNeeds a secure channel (wss, token, etc.)

8. Conclusion

Building a real-time chat service requires an understanding of networking components, databases, and system scalability. The case study above provides a basic picture of implementing WebSocket-based chat, from the backend through to the client. Of course, in an industry or production-scale setting, more attention is needed on aspects such as authentication, rate limiting, message retention, logging, monitoring, and more. Even so, the foundation built from this case study is solid enough to grow into a scalable chat product!

What about the real-time chat architectures you’ve built? Don’t hesitate to share and discuss them in the comments!


Happy coding, and keep sharing knowledge!

Related Articles

💬 Comments