Pub/Sub Plugin
Typed Redis Pub/Sub messaging: real-time events between services and instances, WebSocket broadcasting, and cross-instance signaling — with a @Subscribe decorator and a dedicated subscriber connection managed for you.
Overview
Redis Pub/Sub delivers a published message to every currently-subscribed connection — instantly, across all application instances. The catch: a Redis connection in subscriber mode cannot execute regular commands, so naive setups break their main client. This plugin clones a dedicated subscriber connection from your named client automatically and multiplexes any number of local handlers over it.
| Challenge | Without the plugin | With the Pub/Sub Plugin |
|---|---|---|
| Cross-instance events | Polling or ad-hoc sockets | Instant fan-out via Redis |
| Subscriber-mode connection | Breaks the shared client | Dedicated connection, managed lifecycle |
| Handler wiring | Manual client.on('message') routing | @Subscribe auto-discovery + typed payloads |
Key Features
- Typed publish/subscribe — JSON payloads with generics; non-JSON messages interop as raw strings
- Pattern subscriptions — Redis globs (
user.*,order.?,news.[ab]) with the matching pattern delivered alongside the concrete channel @Subscribedecorator — provider methods auto-subscribed on startup- Local multiplexing — many handlers per channel share one Redis subscription; released when the last unsubscribes
- Channel prefixing — optional namespace that stays invisible to handlers
- Shutdown hygiene — all subscriptions released on module destroy
Installation
npm install @nestjs-redisx/core @nestjs-redisx/pubsub ioredisnpm install @nestjs-redisx/core @nestjs-redisx/pubsub redisBasic Configuration
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { PubSubPlugin } from '@nestjs-redisx/pubsub';
@Module({
imports: [
RedisModule.forRoot({
clients: {
host: 'localhost',
port: 6379,
},
plugins: [
new PubSubPlugin({
channelPrefix: 'app:', // optional namespace for all channels
}),
],
}),
],
})
export class AppModule {}Publishing
import { Injectable, Inject } from '@nestjs/common';
import { PUBSUB_SERVICE, IPubSubService } from '@nestjs-redisx/pubsub';
import { UserCreatedEvent } from './types';
@Injectable()
export class UserPublisher {
constructor(
@Inject(PUBSUB_SERVICE)
private readonly pubsub: IPubSubService,
) {}
async userCreated(user: UserCreatedEvent): Promise<void> {
// Payload is JSON-serialized; returns the number of subscribers reached.
const receivers = await this.pubsub.publish('user.created', user);
if (receivers === 0) {
// Pub/Sub is fire-and-forget: nobody was listening right now.
}
}
}Subscribing with the Decorator
import { Injectable } from '@nestjs/common';
import { Subscribe, IPubSubMessage } from '@nestjs-redisx/pubsub';
import { UserCreatedEvent, NotificationGateway } from './types';
@Injectable()
export class UserEventsHandler {
constructor(private readonly gateway: NotificationGateway) {}
// Auto-subscribed on startup via discovery.
@Subscribe('user.created')
onUserCreated(message: IPubSubMessage<UserCreatedEvent>): void {
this.gateway.broadcast('user:new', message.data);
}
// Redis glob patterns: *, ?, [..]
@Subscribe({ pattern: 'order.*' })
onAnyOrderEvent(message: IPubSubMessage): void {
// message.pattern === 'order.*', message.channel === concrete channel
this.gateway.broadcast(message.channel, message.data);
}
}Pub/Sub is fire-and-forget
Messages are delivered only to connections subscribed at the moment of publishing — there is no persistence, replay, or acknowledgment. For guaranteed, replayable delivery use the Streams plugin.
Documentation
| Topic | Description |
|---|---|
| Configuration | Options and the dedicated subscriber connection |
| @Subscribe Decorator | Auto-discovered channel and pattern handlers |
| Service API | Programmatic publish/subscribe/unsubscribe |
| Recipes | WebSocket broadcast and event patterns |
| Testing | In-memory driver support |
| Troubleshooting | Delivery, drivers, and topology notes |