Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

RedisSocketPubSub

Distributes Socket broadcasts across multiple instances or processes with Redis pub/sub, including sharded pub/sub in Redis Cluster.

server Since 1.0.0 @meekojs/meeko/server Source code
ts
import { RedisSocketPubSub } from "@meekojs/meeko/server";

RedisSocketPubSub is the Socket broker for multiple instances: each channel/room pair is a Redis channel (<prefix>socket:<canal>:<sala>, with channel and room encoded using encodeURIComponent), to which each instance subscribes once while it has local peers in that room. A broadcast in any instance reaches peers in all instances.

Delivery is at most once: Redis pub/sub does not store messages, so anything published while an instance is disconnected from Redis is lost for that instance.

Usage#

ts
// server/runtime.ts
import { createClient } from "redis";
import { defineServerRuntime, RedisSocketPubSub } from "@meekojs/meeko/server";

export default defineServerRuntime(async ({ env }) => {
  const redis = createClient({ url: env.MEEKO_REDIS_URL });
  await redis.connect();
  const subscriber = redis.duplicate(); // the subscriber needs its own connection
  await subscriber.connect();
  return {
    socket: { pubsub: new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "chat:prod:" }) },
    close: async () => { await subscriber.quit(); await redis.quit(); },
  };
});

Type#

ts
class RedisSocketPubSub implements SocketPubSub {
  constructor(options: RedisSocketPubSubOptions);
  channelName(channel: string, room: string): string;
  publish(channel: string, room: string, message: SocketMessage): Promise<void>;
  subscribe(
    channel: string,
    room: string,
    listener: (message: SocketMessage) => void | Promise<void>,
  ): Promise<Unsubscribe>;
}

RedisSocketPubSubOptions

ts
interface RedisSocketPubSubOptions {
  readonly publisher: RedisPublisherClient;
  readonly subscriber: RedisSubscriberClient;
  readonly prefix: string;
  readonly maxMessageBytes?: number;
  readonly sharded?: boolean;
  readonly onError?: (
    error: unknown,
    context: { readonly channel: string; readonly room: string },
  ) => void;
}

RedisPublisherClient

ts
interface RedisPublisherClient {
	publish(channel: string, message: string): Promise<unknown>
	sPublish?(channel: string, message: string): Promise<unknown>
}

RedisSubscriberClient

ts
interface RedisSubscriberClient {
	subscribe(channel: string, listener: RedisMessageListener): Promise<unknown>
	unsubscribe(channel: string, listener: RedisMessageListener): Promise<unknown>
	sSubscribe?(channel: string, listener: RedisMessageListener): Promise<unknown>
	sUnsubscribe?(channel: string, listener: RedisMessageListener): Promise<unknown>
}

Parameters#

new RedisSocketPubSub(options):

Option Type Default Description
publisher RedisPublisherClient required Normal connection with publish(channel, message) (and sPublish with sharded).
subscriber RedisSubscriberClient required Dedicated subscriber-mode connection with subscribe(channel, listener) and unsubscribe(channel, listener) (and sSubscribe/sUnsubscribe with sharded).
prefix string required Unique prefix per application and environment: nonempty, without whitespace and ending in :.
maxMessageBytes number 1048576 (1 MiB) Largest serialized message accepted by publish.
sharded boolean false Uses Redis 7's SPUBLISH/SSUBSCRIBE/SUNSUBSCRIBE, keeping each room on its shard in a cluster.
onError (error, { channel, room }) => void console.error Receives malformed messages and listener failures.

node-redis 4 or later clients implement both interfaces as-is.

Return value#

A SocketPubSub:

Member Description
publish(channel, room, message) Serializes { v: 1, event, payload } with serializeJson and publishes it; resolves when Redis accepts it. Rejects a message larger than maxMessageBytes with SOCKET_LIMIT_EXCEEDED (413).
subscribe(channel, room, listener) Resolves with the Unsubscribe function once Redis confirms the subscription, so no subsequent broadcast is lost. hub.accept awaits it.
channelName(channel, room) The Redis channel for a channel/room pair.

Examples#

Redis Cluster#

ts
// publisher provides sPublish; subscriber provides sSubscribe and sUnsubscribe (Redis 7 or later).
const pubsub = new RedisSocketPubSub({ publisher, subscriber, prefix: "chat:prod:", sharded: true });

Observe discarded messages#

ts
new RedisSocketPubSub({
  publisher: redis,
  subscriber,
  prefix: "chat:prod:",
  onError: (error, { channel, room }) => logger.warn({ channel, room, error }, "socket message dropped"),
});

A message from another format version or malformed (another process publishing to the same channel) is discarded and reported here, without closing any peer.

Deploy with multiple replicas#

yaml
# docker-compose.yml (excerpt): two replicas behind the proxy, one shared Redis
services:
  app:
    image: tienda:1.7.0
    deploy: { replicas: 2 }
    environment:
      MEEKO_PUBLIC_ORIGIN: https://tienda.example.com
      MEEKO_REDIS_URL: rediss://default:${REDIS_PASSWORD}@redis.internal:6380
      MEEKO_TRUST_PROXY: "1"
      MEEKO_HOST: 0.0.0.0

Sessions without configuration already use that Redis; declare the Socket broker in server/runtime.ts as in “Usage”.

Common mistakes#

A prefix without a trailing :#

Incorrectts
new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "chat:prod" });

TypeError: Socket Redis prefix must be nonempty, contain no whitespace, and end with ':', when creating the runtime.

Correctts
new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "chat:prod:" });

sharded with a client lacking sharded pub/sub#

Incorrectts
new RedisSocketPubSub({ publisher: oldClient, subscriber: oldSubscriber, prefix: "chat:prod:", sharded: true });

TypeError: Redis sharded pub/sub requires sPublish, sSubscribe and sUnsubscribe. Requires node-redis with these methods and Redis 7 or later.

Correctts
new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "chat:prod:" }); // without sharded outside a cluster

Use the normal connection as subscriber#

Incorrectts
new RedisSocketPubSub({ publisher: redis, subscriber: redis, prefix: "chat:prod:" });

In subscriber mode, a node-redis connection does not accept other commands: publish (and the session store, if shared) fails on the first broadcast.

Correctts
const subscriber = redis.duplicate();
await subscriber.connect();
new RedisSocketPubSub({ publisher: redis, subscriber, prefix: "chat:prod:" });

Notes#

  • Validation in each instance. The receiving instance validates the payload against the channel schema again and runs authorize and authorizeSend before sending to each local peer.
  • Size. The hub rejects messages larger than its maxMessageBytes (64 KiB by default) before publishing; the broker limit (1 MiB) protects Redis from messages that bypass the hub.
  • Security. Anyone with PUBLISH access to Redis can inject messages into a room; the receiver validates them, but restrict access with ACLs and use a prefix per application and environment.
  • Workers. Not supported: the Worker rejects any broker other than DurableObjectSocketPubSub.
  • Shutdown. The application opens and closes publisher and subscriber.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/server/redissocketpubsub.md