Projects
Dec 22, 2025

Architecture Overview

High-level system design and component interactions

This article provides a comprehensive overview of the messaging system's architecture, including all major components and how they interact.

Component Architecture

Detailed Component Interaction

Service Responsibilities

Backend API

  • Authentication: User login, registration, JWT token generation
  • User Management: Profile updates, contacts management
  • Group Operations: Create groups, add/remove members, manage channels
  • Messaging Operations: Handle message sending and read receipt updates via HTTP
    • Forwards personal and group messages to RabbitMQ
    • Forwards message status updates to RabbitMQ
    • Handle new channels and joining groups
  • REST Endpoints: All stateless operations that don't require real-time communication
  • Database Interface: Direct access to PostgreSQL for data persistence
  • RabbitMQ Publisher: Publishes message events to queue for processing

Socket Server

  • WebSocket Connections: Manages client connections via Socket.IO
  • Real-time Event Delivery: Receives events from workers via RabbitMQ and forwards to connected clients
  • Presence Tracking: Typing indicators and online/offline status
  • Room Management: Handles Socket.IO rooms for group chats and subscription management

Chat Worker

  • Message Consumer: Subscribes to RabbitMQ message queues for incoming events from API
  • Database Persistence: Saves messages and updates to PostgreSQL
  • Read Receipt Processing: Updates message_recipients with delivery status
  • Event Publishing: Sends processed events back to RabbitMQ for socket server distribution

Data Flow Patterns

Real-time Message Flow

Message Processing Flow

Group Message Flow (Channel-Based Routing)

Group Message Flow

Presence Update Flow

Online/Offline Status Flow

Scalability Features

Horizontal Scaling with Multiple Socket Servers

The system supports running multiple Socket.IO instances:

# Docker Compose configuration
socket_server_1:
  environment:
    SERVER_ID: server-1
    RABBITMQ_HOST: rabbitmq
    MEMCACHED_HOST: memcached

socket_server_2:
  environment:
    SERVER_ID: server-2
    RABBITMQ_HOST: rabbitmq
    MEMCACHED_HOST: memcached

Key Benefits:

  • Load balancing across servers (Nginx reverse proxy)
  • Message routing via RabbitMQ (all servers receive events)
  • Shared session state in Memcached (consistent presence across servers)
  • Workers process queued events asynchronously

RabbitMQ Message Queue Pattern

// Socket server publishes to RabbitMQ
await rabbitMQ.publishEvent('messages', {
  type: 'message:sent',
  payload: {
    messageId: msg.id,
    channelId: msg.channel_id,
    senderId: msg.sender_id,
    content: msg.content,
  },
})

// Chat worker consumes from RabbitMQ
consumer.subscribe('messages', async event => {
  if (event.type === 'message:sent') {
    await messageRepository.save(event.payload)
    await messageRecipientRepository.createForChannel(event.payload)
  }
})

This decouples the real-time layer (Socket) from the persistence layer (Worker).

Communication Protocols

Client to Server

  • REST API: HTTP/HTTPS for stateless operations
    • Authentication
    • User profile updates
    • Group management
    • Contact management
  • WebSocket: WSS (Secure WebSocket) for real-time events
    • Messages
    • Typing indicators
    • Read receipts
    • Presence status

Server to Server

  • RabbitMQ (AMQP): Asynchronous event distribution
    • Message events
    • Status events
  • TCP: Direct database connections
    • Data persistence
    • Query operations
  • Memcached Protocol: Binary cache protocol
    • Session storage
    • Status lookups

Database Connection Pattern

// TypeORM manages database connections
const dataSource = new DataSource({
  type: 'postgres',
  host: process.env.DB_HOST,
  port: 5432,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: [User, Message, MessageRecipient, Chat, ...],
  logging: true
})

// Used by Backend API
app.get('/api/chats/:chatId', async (req, res) => {
  const messages = await dataSource.getRepository(Message).find({
    where: { chat_id: req.params.chatId }
  })
})

// Used by Chat Worker
consumer.subscribe('messages', async (event) => {
  await dataSource.getRepository(Message).save(event.payload)
})