Fixing TypeError: Cannot read property ‘client’ of undefined in NestJS with Redis Transport

descricao: “Learn how to fix the TypeError ‘Cannot read property client of undefined’ in NestJS microservices after upgrading to ioredis 5.4. Step‑by‑step guide with code examples.”

# Fixing “TypeError: Cannot read property ‘client’ of undefined” in NestJS microservice when using Redis Transport with ioredis 5.4

I ran into the dreaded `TypeError: Cannot read property ‘client’ of undefined` right after we upgraded our NestJS microservice stack to ioredis 5.4. The service was using the built‑in Redis transport, and everything had been stable for months. Suddenly, the microservice crashed on start‑up, and the stack trace pointed straight at the NestJS Redis client wrapper. In this post I’ll walk you through exactly why the error appears, how ioredis 5.4 changed the initialization contract, and the concrete steps I took to get the microservice back to green.

—

## Why does upgrading to ioredis 5.4 break NestJS Redis transport?

NestJS abstracts the Redis transport behind `@nestjs/microservices`. Internally it creates an instance of `RedisClient` from the `ioredis` package and then decorates it with a few helper methods. Up until version 5.3, `ioredis` exported a **class** named `Redis` that could be instantiated with `new Redis(options)`. Starting with **5.4**, the default export was switched to a **factory function** that returns a client, and the class constructor was removed from the default entry point. The change is subtle but critical: NestJS still does `new Redis(options)` under the hood, which now returns `undefined` and leaves the internal `client` property unset. When NestJS later tries to access `this.client.publish(…)`, you get the `Cannot read property ‘client’ of undefined` error.

The NestJS team has not yet released a patch for this breaking change, so we have to either pin ioredis < 5.4 or adapt the bootstrap code to use the new factory signature.

—

## How can I patch the NestJS Redis transport without downgrading ioredis?

The cleanest approach is to **override the provider** that creates the Redis client and supply our own factory that works with ioredis 5.4. Below is a minimal reproducible example of the broken setup followed by the fixed version.

### Broken setup (pre‑fix)

“`typescript

// app.module.ts – original code before ioredis 5.4

import { Module } from ‘@nestjs/common’;

import { ClientsModule, Transport } from ‘@nestjs/microservices’;

import { RedisOptions } from ‘ioredis’;

const redisOptions: RedisOptions = {

host: ‘localhost’,

port: 6379,

};

@Module({

imports: [

ClientsModule.register([

{

name: ‘REDIS_SERVICE’,

transport: Transport.REDIS,

options: redisOptions,

},

]),

],

})

export class AppModule {}

“`

When the application boots with ioredis 5.4, NestJS throws:

“`

TypeError: Cannot read property ‘client’ of undefined

at RedisClient.<anonymous> (…/node_modules/@nestjs/microservices/client/redis-client.js:45:23)

…

“`

### Fixed setup (override provider)

“`typescript

// redis.provider.ts – custom provider for ioredis 5.4 compatibility

import { Provider } from ‘@nestjs/common’;

import Redis from ‘ioredis’; // default export is now a factory function

import { ClientRedisOptions } from ‘@nestjs/microservices’;

export const RedisClientProvider: Provider = {

provide: ‘REDIS_CLIENT’, // custom token, can be any string

useFactory: (options: ClientRedisOptions) => {

// ioredis 5.4 expects a plain options object, no `new` keyword

const client = Redis(options);

// Optionally attach error handling that NestJS expects

client.on(‘error’, (err) => console.error(‘Redis client error’, err));

return client;

},

inject: [‘REDIS_OPTIONS’], // we will bind this token later

};

“`

“`typescript

// app.module.ts – wiring the custom provider into NestJS

import { Module } from ‘@nestjs/common’;

import { ClientsModule, Transport, ClientRedisOptions } from ‘@nestjs/microservices’;

import { RedisClientProvider } from ‘./redis.provider’;

const redisOptions: ClientRedisOptions = {

host: ‘localhost’,

port: 6379,

};

@Module({

imports: [

ClientsModule.register([

{

name: ‘REDIS_SERVICE’,

transport: Transport.REDIS,

// we still pass the options – they will be injected into our provider

options: redisOptions,

},

]),

],

providers: [

{

provide: ‘REDIS_OPTIONS’,

useValue: redisOptions,

},

RedisClientProvider,

],

exports: [RedisClientProvider],

})

export class AppModule {}

“`

> **Pro Tip:** Keep the custom provider in its own file (`redis.provider.ts`). That way you can reuse the same logic across multiple NestJS modules or even separate microservices without duplicating code.

Now NestJS receives a fully‑initialized `ioredis` client, and the internal `client` property is correctly set. The microservice starts without throwing the TypeError.

—

## What edge cases should I watch out for when customizing the Redis provider?

1. **Connection retries** – The default NestJS Redis transport adds exponential back‑off on connection loss. Our custom provider must replicate that if you rely on it. You can use the `retryStrategy` option that ioredis exposes:

“`typescript

const client = Redis({

host: ‘localhost’,

port: 6379,

retryStrategy(times) {

const delay = Math.min(times * 50, 2000);

return delay;

},

});

“`

2. **Graceful shutdown** – NestJS calls `client.disconnect()` on `onApplicationShutdown`. Our provider returns the raw ioredis instance, which already implements `disconnect()`, so no extra work is needed. Just make sure you don’t wrap the client in another object that hides this method.

3. **Multiple transports** – If you have both Redis and NATS transports in the same app, be careful not to reuse the same token (`REDIS_CLIENT`). Each transport should have its own provider token to avoid accidental cross‑talk.

4. **Version mismatches** – Some third‑party NestJS extensions (e.g., `@nestjs-plus/redis`) still import the old class constructor. Pin those extensions to a version that supports ioredis 5.4 or replace them with the custom provider approach.

—

## How do I verify that the fix really works in a CI pipeline?

I added a tiny e2e test that spins up a real Redis instance via Docker, boots the NestJS microservice, publishes a message, and asserts the response. The test fails with the original error and passes after the provider fix.

“`bash

# docker-compose.yml – minimal Redis for CI

version: ‘3.8’

services:

redis:

image: redis:7-alpine

ports:

– “6379:6379”

“`

“`typescript

// e2e/redis.e2e-spec.ts

import { Test, TestingModule } from ‘@nestjs/testing’;

import { ClientProxy, Transport } from ‘@nestjs/microservices’;

import { AppModule } from ‘../src/app.module’;

import * as Redis from ‘ioredis’;

describe(‘Redis Transport e2e’, () => {

let client: ClientProxy;

beforeAll(async () => {

const module: TestingModule = await Test.createTestingModule({

imports: [AppModule],

}).compile();

client = module.get(‘REDIS_SERVICE’);

await client.connect();

});

it(‘should publish and receive a ping’, async () => {

const result = await client.send({ cmd: ‘ping’ }, {});

expect(result).toBe(‘pong’);

});

});

“`

Running this with the broken setup yields the TypeError; after applying the custom provider, the test passes, giving us confidence that production deployments will behave the same.

—

## FAQ

**How can I keep using the built‑in NestJS Redis transport without a custom provider?**

You can temporarily pin ioredis to `^5.3.2` in your `package.json`. This avoids the breaking change until NestJS releases an official fix.

**Is there a way to monkey‑patch NestJS internals instead of writing a provider?**

You could monkey‑patch `RedisClient` after import, but that is fragile and breaks on future NestJS upgrades. A provider is the recommended, maintainable route.

**Do I need to change my `.env` variables after the fix?**

No. The connection options remain the same; only the way the client is instantiated changes.

**Will this fix work with clustered Redis setups?**

Yes. ioredis 5.4 still supports `new Redis.Cluster([…])`. If you use clustering, expose a separate provider that calls `Redis.Cluster(options)` and inject it where needed.

—

## Conclusion

The `TypeError: Cannot read property ‘client’ of undefined` error in a NestJS microservice using Redis transport is a direct consequence of ioredis 5.4 dropping the `new Redis()` constructor. By **overriding the default client provider** and initializing the client with the new factory signature, we restore compatibility without rolling back to an older ioredis version. The fix is lightweight, testable, and plays nicely with NestJS lifecycle hooks. Keep an eye on the NestJS repository for an upstream patch, but for now the custom provider approach is production‑ready and keeps your microservices humming.

> **Pro Tip:** When you introduce a custom provider, add it to a shared `CoreModule` that re‑exports the provider. This centralizes the Redis client configuration and prevents accidental multiple instances across your codebase.

Stay tuned for more deep dives into NestJS quirks and real‑world fixes.

—

*Follow SpiritCode for more hands‑on engineering stories like this.*