node-cache-manager-redis-store
node-cache-manager-redis-store copied to clipboard
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 {} `
The same issue
Same Issue
Same issue
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.
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
@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.
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);
}
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 ^^^
@dabroek
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;
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, }),
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
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',
}),
],
})
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
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
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.
The solution mentioned by @cgat is working. If we use cache-manager-redis-store
2.0. It fixes the problem
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.
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 {}
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
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 {}
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
@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. 🍻
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,
};
},
}),
My solution for cache-manager v5 + cache-manager-redis-yet: https://github.com/node-cache-manager/node-cache-manager/issues/210#issuecomment-1519127408
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;
}
}
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',
};
}
},
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 {}
none of this works for me, I'm trying to get information from redis but I. don't get anything from the Redis database