skip to Main Content

If redis is up and running and I start my nestjs app, works as expected. If redis goes down while my nestjs app is running, no problem, I have event listeners and it will keep trying to reconnect etc. But, if redis is down before I try to start my nestjs app, the app does not start. I’ve looked everywhere and cannot find a way to basically ignore the error. I want my app to proceed booting up even if redis connection cannot be established. Here are the relevant code pieces.

package.json

{
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/config": "^3.2.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "@nestjs/cache-manager": "^2.2.1",
    "@nestjs/schedule": "^4.0.1",
    "cache-manager": "^5.4.0",
    "cache-manager-redis-store": "^3.0.1",
    "reflect-metadata": "^0.2.0",
    "rxjs": "^7.8.1"
  }
}

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    abortOnError: false,
  });

  await app.listen(3000);
}

bootstrap();

app.module.ts

import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { RedisClientEventListener, RedisOptions } from './configs/redis.config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    CacheModule.registerAsync(RedisOptions),
    ScheduleModule.forRoot(),
  ],
  controllers: [...],
  providers: [...],
})
export class AppModule extends RedisClientEventListener {}

redis.config.ts

import { CACHE_MANAGER, CacheModuleAsyncOptions } from '@nestjs/cache-manager';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { RedisStore, redisStore } from 'cache-manager-redis-store';
import { Inject, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { CacheService } from '../services/cache.service';

export const RedisOptions: CacheModuleAsyncOptions = {
  isGlobal: true,
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => {
    const store = await redisStore({
      socket: {
        host: configService.get<string>('API_REDIS_HOST'),
        port: parseInt(configService.get<string>('API_REDIS_PORT')!),
        connectTimeout: 3000,
        reconnectStrategy(retries: number): number | Error {
          if (retries > 39) {
            console.log('Redis - Max Connection Retries - Exiting')
          }
          return 3000;
        },
        tls: process.env.NODE_ENV !== 'local',
        rejectUnauthorized: false,
      },
    });
    return {
      store: () => store,
    };
  },
  inject: [ConfigService],
};

export class RedisClientEventListener implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RedisClientEventListener.name);
  private readonly redisStore: RedisStore;

  constructor(@Inject(CACHE_MANAGER) readonly cacheManager: Cache) {
    this.redisStore = cacheManager.store as unknown as RedisStore;
  }

  onModuleInit(): void {
    console.log("starting AppModule...")
    const client = this.redisStore.getClient();

    if (!client) {
      this.logger.error('no redis client initialised');
      CacheService.isHealthy = false;
    }

    if (client.isReady) {
      this.logger.log('redis client is connected and ready'); // initial connection to redis upon node app starting, never triggered again
      CacheService.isHealthy = true;
    }

    client.on('connect', () => {
      this.logger.log('redis client is connecting to server...');
    });

    client.on('ready', () => {
      this.logger.log('redis client is ready'); // after re-connecting to redis successfully this gets triggered
      CacheService.isHealthy = true;
    });

    client.on('end', () => {
      this.logger.log('redis client connection closed');
      CacheService.isHealthy = false;
    });

    client.on('error', (error: Error) => {
      this.logger.error(error, 'redis client received an error'); // redis server dies/connection stopped this gets triggered
      CacheService.isHealthy = false;
    });
  }

  async onModuleDestroy(): Promise<void> {
    await this.redisStore.getClient().quit();
  }
}

The result of me running npm run start with Redis shutdown

[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [NestFactory] Starting Nest application...
[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [InstanceLoader] ConfigHostModule dependencies initialized +10ms
[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [InstanceLoader] DiscoveryModule dependencies initialized +0ms
[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 7572  - 02/28/2024, 9:41:26 AM     LOG [InstanceLoader] ScheduleModule dependencies initialized +6ms
[Nest] 7572  - 02/28/2024, 9:41:26 AM   ERROR [ExceptionHandler] connect ECONNREFUSED ::1:6379
Error: connect ECONNREFUSED ::1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16)
Error: connect ECONNREFUSED ::1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16)

2

Answers


  1. I think this one is breaking DI(Dependency injection) pattern, DI need to instantiate the class before they use, otherwise you should not use like DI.

    Login or Signup to reply.
  2. can you provide also CacheService please?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search