Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incompatible with NestJS cache store #53

Open
consult-kk opened this issue Nov 3, 2022 · 38 comments · Fixed by kin-labs/kinetic#523
Open

Incompatible with NestJS cache store #53

consult-kk opened this issue Nov 3, 2022 · 38 comments · Fixed by kin-labs/kinetic#523

Comments

@consult-kk
Copy link

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 {}
`

@dimaqw
Copy link

dimaqw commented Nov 6, 2022

The same issue

@Alfagun74
Copy link

Same Issue

@belyaev-dev
Copy link

Same issue

@dougrocha
Copy link

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.

@Alfagun74
Copy link

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

@MikeRossXYZ
Copy link

@dougrocha You are effectively bypassing the Typescript type checks, which I would not recommend

I'm just stepping back into the Typescript and Node world again after a few years working in Go and Python, but I believe my #54 ⬆️ should get things working in NestJS by narrowing the type definitions provided by this package.

If @dabroek approves of the changes, then you should be unblocked.

@iangregsondev
Copy link

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);
    }

@iangregsondev
Copy link

My solution above is a bit messy, first I had to use "as any" to bypass type issue but should not all providers support milliseconds? Otherwise you need to add some logic as my reply above ^^^

@luluhoc
Copy link

luluhoc commented Dec 7, 2022

@dabroek

@GabSnow24
Copy link

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;

@cgat
Copy link

cgat commented Dec 11, 2022

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.

@marianolinares
Copy link

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, }),

@Pangeran29
Copy link

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

@yusuftaufiq
Copy link

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',
    }),
  ],
})

@puckj
Copy link

puckj commented Feb 16, 2023

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

Thank you king

@AhmedHdeawy
Copy link

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

Thanks

@Exilz
Copy link

Exilz commented Feb 17, 2023

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.

@pratyush-prateek
Copy link

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

@dev-whoan
Copy link

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

Thanks It works.

@ram95krishh
Copy link

ram95krishh commented Feb 28, 2023

My solution with a dummy store fallback

import {
  CacheModule,
  CacheModuleOptions,
  CacheStore,
  Module
} from '@nestjs/common'
import { redisStore } from 'cache-manager-redis-store'

class DummyFallBackStore implements CacheStore {
  get(key: string) {
    console.debug(`Dummy Cache :: gets nothing for key - ${key}`)
    return Promise.resolve(undefined)
  }
  set(key: string) {
    console.debug(`Dummy Cache :: sets nothing for key - ${key}`)
    return Promise.resolve(undefined)
  }
  del(key: string) {
    logger.debug(`Dummy Cache :: deletes nothing for key - ${key}`)
    return Promise.resolve(undefined)
  }
}

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: async () : Promise<CacheModuleOptions> => {
        try {
          const store = await redisStore({
            url: '<ConnectionString>'
          })
          return { store }
        } catch (error) {
          console.error(error)
          return { store: new DummyFallBackStore() }
        }
      }
    })
  ],
  controllers: [<MyController>]
})
export class MyModule {}

@CarsonRoscoe
Copy link

CarsonRoscoe commented Mar 2, 2023

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

@MaksimKiselev
Copy link

MaksimKiselev commented Mar 17, 2023

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: #53 (comment)

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 {}

@alexphamhp
Copy link

Thanks @MaksimKiselev
I tried your solution and it works
But, I just want to note that
the TTL when I set, it is miliseconds not seconds

@MaksimKiselev
Copy link

@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. 🍻

@diariowebsquad
Copy link

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,
        };
      },
    }),

@suoncha
Copy link

suoncha commented Apr 23, 2023

My solution for cache-manager v5 + cache-manager-redis-yet:
jaredwray/cacheable#210 (comment)

@tushermahmud
Copy link

Here is my solution. Node v:16.18.0 npm v:8.19.2

"@nestjs/cache-manager": "^1.0.0", "@nestjs/common": "^9.0.0", "@nestjs/core": "^9.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/platform-express": "^9.0.0", "@nestjs/typeorm": "^9.0.1", "bcrypt": "^5.1.0", "bcryptjs": "^2.4.3", "cache-manager": "^5.2.1", "cache-manager-redis-store": "^2.0.0", "cache-manager-redis-yet": "^4.1.1",

import {  Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { JwtModule } from '@nestjs/jwt';
import * as redisStore from 'cache-manager-redis-store';
@Module({
    imports:[JwtModule.register({ secret: 'secret', signOptions:{expiresIn:'1d'} }), CacheModule.register({
        store: redisStore as any,
        host: 'redis',
        port: 6379,
    })],
    exports:[JwtModule, CacheModule]
})
export class SharedModule {}

import { Body, Controller, Get, Post, Param, ParseIntPipe, Put, Delete, UseGuards, UseInterceptors, Inject } from '@nestjs/common';
import { AuthGuard } from 'src/auth/auth.guard';
import { ProductCreateDto } from './dtos/product-create.dto';
import { ProductService } from './product.service';
import { CacheKey,CacheInterceptor,CacheTTL, CACHE_MANAGER } from '@nestjs/cache-manager';
import {Cache} from 'cache-manager'

@controller()
export class ProductController {
constructor(private productService:ProductService, @Inject(CACHE_MANAGER) private cacheManager:Cache){
}

@UseGuards(AuthGuard)
@Get('admin/products')
async getAllProducts(){
    return await this.productService.findAll({});
}

@UseGuards(AuthGuard)
@Post('admin/products')
async create(@Body() body:ProductCreateDto){
    return await this.productService.save(body)
}

@UseGuards(AuthGuard)
@Get('admin/products/:id')
async getProduct(@Param('id', ParseIntPipe) id:number){
    return await this.productService.findById(id)
}

@UseGuards(AuthGuard)
@Put('admin/products/:id')
async update(@Param('id',ParseIntPipe) id:number, @Body() body:ProductCreateDto){
    await this.productService.update(id, body);
    return await this.productService.findById(id);
}

@UseGuards(AuthGuard)
@Delete('admin/products/:id')
async delete(@Param('id') id:number){
    await this.productService.delete(id);
}

@CacheKey('products_frontend')
@CacheTTL(30*60)
@UseInterceptors(CacheInterceptor)
@Get('ambassador/products/frontend')
async frontend(){
    return await this.productService.findAll({})
}

@Get('ambassador/products/backend')
async backend(){
    let products = await this.cacheManager.get('products_backend');
    if(!products) {
        products = await this.productService.findAll({})
        await this.cacheManager.set('products_backend',products, 1800)
    }
    return products;
}

}

@jongomes
Copy link

jongomes commented Jul 20, 2023

CacheModule.registerAsync({
  isGlobal: true,
  useFactory: async () => {

    try {
      const store = await redisStore({
        url: process.env.REDIS,
        ttl: 60000,
        fallback: 'memory' -- it would be interesting to fall back
      });
      return {store};
    } catch (error) {
      return {
        store: 'memory',
      };
    }
  },

@alhajee
Copy link

alhajee commented Jul 27, 2023

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 {}

@arlan85
Copy link

arlan85 commented Jul 31, 2023

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

@LebranD
Copy link

LebranD commented Sep 21, 2023

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;
           },
       }),

@cristian-moreno-ruiz
Copy link

It looks like this package is not maintained any more, and, although I'm facing the same issue at the moment, I moved to this other (which is a fork from this one but actively maintained):

https://github.com/node-cache-manager/node-cache-manager-redis-yet

Also, created this issue to be properly tracked:

https://github.com/node-cache-manager/node-cache-manager-redis-yet/issues/394

@janardhan5031
Copy link

janardhan5031 commented Feb 3, 2024

I found a thing , when i save key value with TTL in redis from nestjs server after ttl expired, the key value not deleting.
Below is my code, let me know if anything went wrong.
Thank you.
`import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CacheModule, CacheStore } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';

@module({
imports: [
CacheModule.registerAsync({
useFactory: async () => {
const store = await redisStore({
socket: {
host: 'localhost',
port: 6379,
},
password: '1234',
});

    return {
      store: store as unknown as CacheStore,
    };
  },
}),

],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
import { Cache } from 'cache-manager';
import { CACHE_MANAGER } from '@nestjs/cache-manager';

@controller()
export class AppController {
constructor(
private readonly appService: AppService,
@Inject(CACHE_MANAGER) private cacheModule: Cache,
) {}

@get('health-check')
async getHello() {
const data = await this.cacheModule.get('HELLO');
if (data) {
return data;
}

await this.cacheModule.set('HELLO', 'Hello from store', 10);
return 'Hello from server';

}
}`

@NathBabs
Copy link

NathBabs commented Feb 5, 2024

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

I am currently facing this issue, seems it is not using my redis but rather memory.

@phankieuphu
Copy link

I found solution

  import * as redisStore from 'cache-manager-redis-yet';
 
  @Module({
    imports: [
      CacheModule.register({
        isGlobal: true,
        useFactory: () => ({
          store: redisStore,
        }),
        host: config.REDIS.HOST,
        port: config.REDIS.PORT,
      }),

@mariem2189
Copy link

I found a way to fix this issue, casting redisStore to 'any' to bypass the TypeScript error:

import * as redisStore from 'cache-manager-redis-store';

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

@buidaoanhvan
Copy link

buidaoanhvan commented Oct 15, 2024

import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-yet';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      useFactory: async () => ({
        store: await redisStore({
          socket: {
            host: 'localhost',
            port: 6379
          }
        })
      })
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

@kgkg
Copy link

kgkg commented Oct 26, 2024

None of above solution works for me. It seems to work because in-memory cache is used silently instead of Redis.

Try turning off Redis (image or service) and try to check if it even connects to it.

Working solution as of Nov 2024 with getting values from ConfigModule:

  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService): Promise<CacheModuleOptions> => {
        try {
          const host = configService.get<string>('REDIS_HOST');
          const port = configService.get<number>('REDIS_PORT');
          const store = await redisStore({
            url: `redis://${host}:${port}`,
          });
          return { store };
        } catch (error) {
          console.error(error);
        }
      },
    }),
  ],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.