qus-node-redis-cache
Version:
redis cache for nestjs
115 lines (99 loc) • 4.35 kB
text/typescript
/*
* MIT License
*
* Copyright (c) 2025 Quantum Unit Solutions
* Author: David Meikle
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { Injectable, Logger } from '@nestjs/common';
import { RedisClientType, createClient } from 'redis';
interface BaseDto {
id: string;
}
()
export abstract class CachingService<T extends BaseDto> {
protected _client: RedisClientType | null = null;
protected logger: Logger = new Logger(this.constructor.name);
protected url: string = process.env.REDIS_SERVER ?? 'redis://localhost:6379';
constructor(
protected ttl: number,
private keyPrefix: string,
url?: string,
) {
if(url) {
this.url = url;
}
this._client = createClient({ url: this.url });
this._client.connect().catch(err => this.logger.error('Redis connection error', err));
}
protected getClient(): RedisClientType {
if (!this._client) {
throw new Error('Redis client not obtained');
}
return this._client;
}
protected async getFromCache(key: string, fetchData: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> {
const client: RedisClientType = this.getClient();
const prefixedKey = `${this.keyPrefix}${key}`;
if (await client.exists(prefixedKey)) {
const cacheResult: string | null = await client.get(prefixedKey);
if (cacheResult) {
return JSON.parse(cacheResult) as T;
}
}
const result: T = await fetchData(key, ...args);
await client.setEx(prefixedKey, this.ttl, JSON.stringify(result));
return result;
}
protected async getListFromCache(keys: string[], fetchAllData: (keys: string[]) => Promise<T[]>): Promise<T[]> {
const client: RedisClientType = this.getClient();
const missing: string[] = [];
const existing: T[] = [];
const prefixedKeys: string[] = keys.map(key => `${this.keyPrefix}${key}`);
const cacheResults: (string | null)[] = await Promise.all(prefixedKeys.map((key: string) => client.get(key)));
for (let i = 0; i < keys.length; i++) {
const cacheResult = cacheResults[i];
if (cacheResult) {
existing.push(JSON.parse(cacheResult));
} else {
missing.push(keys[i]);
}
}
if (missing.length > 0) {
const results = await fetchAllData(missing);
const setExPromises = results.map(item => {
const keyAsString = String(item.id);
const prefixedKey = `${this.keyPrefix}${keyAsString}`;
existing.push(item);
return client.setEx(prefixedKey, this.ttl, JSON.stringify(item));
});
await Promise.all(setExPromises);
}
return existing;
}
protected getKeyPrefix(): string {
return this.keyPrefix;
}
async clearCache(key: string): Promise<void> {
const client: RedisClientType = this.getClient();
const prefixedKey = `${this.keyPrefix}${key}`;
await client.del(prefixedKey);
}
}