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
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.
can you provide also CacheService please?