Skip to content
NestJS ns microservices 5 min read

Redis Transport

The Redis transporter lets NestJS microservices communicate over Redis using its publish/subscribe mechanism. It is lightweight, trivial to operate (most teams already run Redis for caching), and a natural fit for fire-and-forget event broadcasting. Because Redis Pub/Sub does not persist messages or guarantee delivery, it shines for events where occasional loss is acceptable but is the wrong tool when you need durable, at-least-once queues.

How the Redis transporter works

NestJS implements request/response and event semantics on top of Redis channels. For every pattern, the framework derives two channels: one for the request (the pattern itself) and one for the reply (the pattern suffixed for the response). When a client emits or sends, it PUBLISHes to the request channel; the server SUBSCRIBEs to it, handles the message, and—for request/response—publishes the result back on the reply channel.

This is plain Redis Pub/Sub, which means messages are delivered only to subscribers connected at the moment of publish. There is no buffering, no acknowledgement, and no replay. A server that is restarting will miss every message published during the gap.

Warning: Redis Pub/Sub is at-most-once. If you require guaranteed delivery, retries, or durable queues, use the RabbitMQ or Kafka transporters instead. Note that NestJS uses Pub/Sub channels, not Redis Streams.

Installing dependencies

The Redis transporter is driven by the ioredis client.

npm install @nestjs/microservices ioredis

Configuring the server

Bootstrap a microservice with Transport.REDIS. The host and port point at your Redis instance; everything under options is passed through to the transporter.

// main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.REDIS,
      options: {
        host: 'localhost',
        port: 6379,
        retryAttempts: 5,
        retryDelay: 3000,
      },
    },
  );

  await app.listen();
}
bootstrap();

Message and event handlers are declared with the standard decorators. @MessagePattern participates in request/response; @EventPattern consumes one-way events.

// orders.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';

interface OrderCreatedEvent {
  orderId: string;
  total: number;
}

@Controller()
export class OrdersController {
  @MessagePattern({ cmd: 'sum' })
  accumulate(@Payload() data: number[]): number {
    return (data || []).reduce((acc, n) => acc + n, 0);
  }

  @EventPattern('order.created')
  handleOrderCreated(@Payload() event: OrderCreatedEvent): void {
    console.log(`Fulfilling order ${event.orderId} ($${event.total})`);
  }
}

Output:

Fulfilling order 1a2b-3c4d ($129.50)

Configuring the client

A consumer registers a ClientProxy with matching connection options, typically via ClientsModule.

// app.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { GatewayController } from './gateway.controller';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'ORDERS_SERVICE',
        transport: Transport.REDIS,
        options: { host: 'localhost', port: 6379 },
      },
    ]),
  ],
  controllers: [GatewayController],
})
export class AppModule {}

Inject the client and use send for request/response (returns an Observable) or emit for events.

// gateway.controller.ts
import { Controller, Get, Post, Body, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Observable } from 'rxjs';

@Controller('orders')
export class GatewayController {
  constructor(@Inject('ORDERS_SERVICE') private readonly client: ClientProxy) {}

  @Get('sum')
  sum(): Observable<number> {
    return this.client.send<number, number[]>({ cmd: 'sum' }, [1, 2, 3, 4]);
  }

  @Post()
  create(@Body() body: { orderId: string; total: number }): void {
    this.client.emit('order.created', body);
  }
}

Tip: emit does not wait for a handler and is the right choice for Redis events. send opens a reply subscription and times out if no server responds—avoid it for pure broadcast scenarios.

Connection options

These options live under the options key and are forwarded to ioredis.

OptionTypeDefaultPurpose
hoststringlocalhostRedis server hostname.
portnumber6379Redis server port.
retryAttemptsnumber0Number of reconnection attempts after a connection drop.
retryDelaynumber0Delay in milliseconds between reconnection attempts.
wildcardsbooleanfalseUse PSUBSCRIBE glob patterns instead of exact channels.
passwordstringAuth password (or use a connection URL).
tlsobjectTLS settings for secured Redis (e.g. managed cloud).

You can also point at a Redis URL directly, which is convenient for managed providers:

options: {
  host: process.env.REDIS_HOST,
  port: Number(process.env.REDIS_PORT),
  password: process.env.REDIS_PASSWORD,
  tls: process.env.REDIS_TLS ? {} : undefined,
}

Redis pub/sub versus brokered queues

Choosing a transporter is mostly a question of delivery guarantees and operational weight.

ConcernRedis Pub/SubRabbitMQ / Kafka
Delivery guaranteeAt-most-onceAt-least-once (durable)
Message persistenceNoneYes
Offline consumerMisses messagesReceives on reconnect
Operational costVery lowHigher
LatencyExtremely lowLow–moderate
Best forCache invalidation, presence, transient eventsOrders, payments, work queues

Reach for Redis when the event is informational and replaceable: cache busting, live dashboards, “user is typing” signals. Reach for a broker when missing a message is a business problem.

Best practices

  • Prefer @EventPattern/emit over @MessagePattern/send—Redis Pub/Sub is built for broadcast, not durable request/response.
  • Treat events as disposable: never put data through Redis Pub/Sub that you cannot afford to lose.
  • Set retryAttempts and retryDelay so transient network blips do not permanently detach a service.
  • Reuse your existing caching Redis only for low-stakes signals; isolate critical messaging onto a broker.
  • Secure connections in production with password and tls, and load credentials from environment variables.
  • Namespace your patterns (e.g. order.created, user.updated) to keep channels readable and collision-free.
  • Make event handlers idempotent—even at-most-once delivery can produce duplicates under reconnection edge cases.
Last updated June 14, 2026
Was this helpful?