Skip to main content

Realtime Messaging


Publish/subscribe (pub/sub) is a messaging pattern where producers send messages to a topic, and all subscribers listening to that topic receive a copy.

How It Works

┌──────────┐          ┌─────────┐          ┌──────────────┐
│ Producer │ ──────▶ │ Topic │ ──────▶ │ Subscriber A │
└──────────┘ │ │ └──────────────┘
│ │ ┌──────────────┐
│ │ ──────▶ │ Subscriber B │
│ │ └──────────────┘
│ │ ┌──────────────┐
│ │ ──────▶ │ Subscriber C │
└─────────┘ └──────────────┘
  1. Producer publishes a message to a topic
  2. RelayX routes the message to all active subscribers
  3. Each subscriber receives its own copy

Publishers don't know who's subscribed. Subscribers don't know who published. The only shared agreement is the topic name.

Fire-and-Forget

From the producer's perspective, publishing is fire-and-forget:

// Producer doesn't wait for subscribers to process
await client.publish('notifications.user.123', {
type: 'friend_request',
from: 'user_456'
});

// Execution continues immediately
console.log('Message sent!');

The producer doesn't know (or care) about:

  • How many subscribers exist
  • Whether subscribers processed successfully
  • How long processing takes

This decoupling allows producers and consumers to evolve independently.

Fan-Out: Multiple Subscribers

Every subscriber receives every message. This is called fan-out:

// Subscriber 1: Send push notification
await client.on('orders.created', async (msg) => {
await sendPushNotification(msg.data.userId, 'Order confirmed!');
});

// Subscriber 2: Update analytics
await client.on('orders.created', async (msg) => {
await analytics.track('order_created', msg.data);
});

// Subscriber 3: Notify warehouse
await client.on('orders.created', async (msg) => {
await warehouse.queueForPacking(msg.data.orderId);
});

// When ONE message is published, ALL THREE subscribers execute

When to Use Messaging

Messaging is ideal when:

Use CaseExample
Event broadcastingUser signs up → send email, create CRM record, notify sales
Real-time updatesChat messages, live notifications, collaborative editing
Decoupled servicesOrder service publishes; inventory, shipping, analytics consume
Audit loggingEvery action publishes an event; logging subscriber captures all

When NOT to Use Messaging

Messaging is not the right choice when:

  • Only one consumer should handle each message → Use Queues
  • You need delivery confirmation → Messaging is fire-and-forget
  • Subscriber might be offline → Use Queues for durable delivery

Example: Real-Time Chat

A classic messaging use case:

const roomId = 'room-123';

// Subscribe to room messages
await client.on(`chat.room.${roomId}`, (msg) => {
displayMessage(msg.data.userId, msg.data.text);
});

// Send a message
await client.publish(`chat.room.${roomId}`, {
userId: currentUser.id,
text: 'Hello everyone!',
timestamp: Date.now()
});

Every user subscribed to the room topic receives every message in real-time.

Subscriber Lifecycle

Subscribers only receive messages while actively connected:

// Start receiving messages
await client.on('events.live', handler);

// ... messages flow to handler ...

// Stop receiving messages
await client.off('events.live');

// Messages sent now are NOT received

If a subscriber disconnects and reconnects, it misses messages sent during the gap. For durable, guaranteed delivery, use Queues.

Common Mistakes

Treating messaging like a queue

Messaging delivers to all subscribers, not just one. If you need load balancing (one worker handles each job), use Queues.

Expecting exactly-once delivery

Subscribers may receive the same message more than once if they crash during processing. Design your handlers to be idempotent.

Relying on offline delivery

If no subscribers are connected, messages are not stored (unless you use message history). For guaranteed delivery, use Queues.



Need Help?

Join our Discord server, post your concern & someone from our team will help you out ✌️