node-cache-manager-redis-store: Incompatible with NestJS cache store

V3.0.1 is incompatible with NestJS . Unable to register CacheModule using the RedisStore.

"message": "Type 'typeof import(\"cache-test/node_modules/cache-manager-redis-store/dist/index\")' is not assignable to type '(string | CacheStoreFactory | CacheStore) & typeof import(\"/Users/kk/dev/nodejs/cache-test/node_modules/cache-manager-redis-store/dist/index\")'.\n  ...

Here is the code I am using in the module.ts : ` import * as redisStore from “cache-manager-redis-store”; import { ConfigModule } from ‘@nestjs/config’;

@Module({ imports: [ ConfigModule.forRoot(), CacheModule.register({ isGlobal: true, store: redisStore, url: “redis://localhost:6379”, }), HttpModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {} `

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 33
  • Comments: 35

Most upvoted comments

Hello i solve this problem with down grade version of some packages, go check here https://github.com/Pangeran29/testing-nestjs/blob/master/src/testing-cache-redis/testing-cahce-redis.MD

Problem

Example from official docs works only for cache-manager-redis-store@^2.0.0

cache-manager-redis-store@^2.0.0 incorrectly implement CacheManager interface and accepts TTL as object where ttl property should be in seconds { ttl: TTL_IN_SECONDS } see: https://github.com/dabroek/node-cache-manager-redis-store/issues/53#issuecomment-1325108666

Solution

The node-cache-manager have now official redis/ioredis cache stores.

npm i --save cache-manager-redis-yet

import { CacheModule, Module } from '@nestjs/common';
import { redisStore } from 'cache-manager-redis-yet';

@Module({
  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      useFactory: async () => ({
        store: await redisStore({
          host: process.env.REDIS_HOST,
          port: parseInt(process.env.REDIS_PORT!),
        }),
      }),
    }),
  ],
})
export class AppModule {}

I managed to go through this error using require

When i used import and bypass using some weird thing in typescript i cant get anything in Redis container and was receiveng some errors in cache service. by using the code above, i managed to do things work. const redisStore = require('cache-manager-redis-store').redisStore;

cache.module.ts

I personally don’t like having my configs directly in my App Module. I separate my configs into different modules and import them into App module.

You’d also notice that I prefer to read my environment variables from the ConfigService instead of reading directly from process.env (This way you have more control for where env files are being read from)

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CacheModule, CacheModuleAsyncOptions } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      imports: [ConfigModule], // No need to call ConfigModule.forRoot again
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        const isActive = ['true', '1', 'yes'].includes(
          configService.get<string>('API_REDIS_STORE_IS_ACTIVE'),
        );
        return {
          store:
            isActive &&
            (await redisStore({
              // Store-specific configuration:
              socket: {
                host: configService.get<string>('API_REDIS_HOST'),
                port: +configService.get<number>('API_REDIS_PORT'),
              },
            })), // use redis when available or default to cache store
          ttl: 5000, // milliseconds
          max: 10, // maximum number of items in cache
        } as CacheModuleAsyncOptions;
      },
    }),
  ],
})
export class CacheConfigModule {}

Referring to this comment, I think this issue can be solved if you use node-cache-manager-redis-yet instead.

import { CacheModule, Module } from '@nestjs/common';
import { redisStore } from 'cache-manager-redis-yet';

@Module({
  imports: [
    CacheModule.register({
      store: redisStore,
      url: 'redis://localhost:6379',
    }),
  ],
})

For NestJs 8, I wasn’t able to get any of the solutions to work.

One solution shown in the NestJs does work

CacheModule.register<ClientOpts>({
      store: redisStore,

      // Store-specific configuration:
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT),
      password: process.env.REDIS_PASSWORD,
    }),

But you need to ensure that this is using cache-manager-redis-store 2.0.

The problem is redisStore is of type Store, and CacheStore is required. My solution is casting redisStore as CacheStore to fix it.

CacheModule.register({ store: redisStore as unknown as CacheStore, host: 'localhost', port: 6379, }),

Also there is another issue. Here is my solution. The in memory provider supports milliseconds by default but the redis provider (this one) supports seconds.

And as others have mentioned, the 3rd argument on the standard in-memory provider takes a number but this provider needs an object with key “ttl” and a number.

More info here…

    // It supports seconds and NOT milliseconds
    if (this.configService.cacheProvider == CacheProvider.redis) {
      // support seconds!!
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      await this.cacheManager.set(apiKey, key, { ttl: this.configService.cacheTtl / 1000 } as any);
    } else {
      // rest of providers should support milliseconds
      await this.cacheManager.set(apiKey, key, this.configService.cacheTtl);
    }

This package is riddled with issues, bad typing, missing methods that are not exposed and so on. I appreciate it for bridging nest’s cache manager and redis but at this point I think it’s easier to use something like ioredis with very basic abstractions for the set and get methods, and just be done with it.

This issue prevents me from properly closing the connection after running my end to end tests. Previously I couldn’t set redis clusters properly because of the way the client is set up and exposed through this additional layer. It works great for simpler projects anyway.

I found a way to fix this issue although it may not be nice.

import { redisStore } from 'cache-manager-redis-store'

CacheModule.registerAsync({
      isGlobal: true,
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        ttl: configService.get('CACHE_TTL'),
        store: (await redisStore({
          url: configService.get('REDIS_URL'),
        })) as unknown as CacheStore,
      }),
      inject: [ConfigService],
    }),

I’m not exactly sure what’s going on outside of the fact the types don’t match.

here is my solution

CacheModule.registerAsync<RedisClientOptions>({
           isGlobal: true,
           imports: [ConfigModule],
           inject: [ConfigService],
           useFactory: async (configService: ConfigService) => {
               const redis = configService.get('redis');
               return {
                   store: redisStore,
                   host: redis.host,
                   port: redis.port,
               } as StoreConfig;
           },
       }),

@alexphamhp I meant that cache-manager-redis-store@^2.0.0 incorrectly implement CacheManager interface and accept ttl parameter as object with ttl property where ttl in seconds.

CacheManager interface define ttl parameter as number where ttl must be in microseconds.

I’ve update original comment. Thx. 🍻

none of this works for me, I’m trying to get information from redis but I. don’t get anything from the Redis database

this correct solution:

CacheModule.registerAsync<any>({
      isGlobal: true,
      imports: [ConfigModule],
      useFactory: async () => {
        const store = await redisStore({
          url: 'redis://redis_listings_service:6379',
          ttl: 0,
        });
        return {
          store: () => store,
        };
      },
    }),

Can’t take credit, but chiming in, this solution worked for me in app.module.

import { redisStore } from 'cache-manager-redis-store';
...
CacheModule.registerAsync < any > ({
    isGlobal: true,
    imports: [ConfigModule],
    useFactory: async (configService: ConfigService) => {
        const store = await redisStore({
            url: configService.get('REDIS_URL'),
            ttl: 60,
        });
        return {
            store: () => store,
        };
    },
    inject: [ConfigService],
}),

Do note, for some reason ttl changed from milliseconds to seconds with this change, despite the in-memory caching being in milliseconds with my environment

The solution mentioned by @cgat is working. If we use cache-manager-redis-store 2.0. It fixes the problem

I found a way to fix this issue although it may not be nice.

import { redisStore } from 'cache-manager-redis-store'

CacheModule.registerAsync({
      isGlobal: true,
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        ttl: configService.get('CACHE_TTL'),
        store: (await redisStore({
          url: configService.get('REDIS_URL'),
        })) as unknown as CacheStore,
      }),
      inject: [ConfigService],
    }),

I’m not exactly sure what’s going on outside of the fact the types don’t match.

Nah thanks imma stay on the old version until something proper gets released