A simple node cache api
If one wants cross request cache queries in a node server, one can use a simple cache api inspired by react-query with only a redis library implementation.
Caching by query key
import Redis from 'ioredis'// or setup a redis connection in a separate file with creds etc// import { redis } from '@/server/redis' // ioredis instance redis = new Redis()const redis = new Redis()type CacheOptions = { ttlInSeconds: number // seconds skipCache?: boolean}// An api similar to react query's useQueryexport async function cacheQuery<T>({ queryKey, queryFn, options = { ttlInSeconds: 60 * 5, skipCache: false, },}: { queryKey: QueryKey queryFn: () => Promise<T> options?: CacheOptions}): Promise<T> { const key = hashKey(queryKey) if (!options?.skipCache) { const cached = await redis.get(key) if (typeof cached === 'string') { try { if (process.env.LOG_LEVEL === 'debug') { console.log('Cache hit', key) } const parsed = JSON.parse(cached) return parsed as T } catch (e) { console.error('Error parsing cached data', e) } } } return queryFn?.().then(async (data) => { if (process.env.LOG_LEVEL === 'debug') { console.log('Cache miss', key) } try { await redis?.set?.(key, JSON.stringify(data), 'EX', options?.ttlInSeconds) } catch (e) { console.error('Error caching data', e) } return data })}/** * Default query & mutation keys hash function. * Hashes the value into a stable hash. * https://github.com/TanStack/query/blob/69d37f33bdee50d73d0f05256f243113a857a1ee/packages/query-core/src/utils.ts#L177 */export type QueryKey = ReadonlyArray<unknown>export function hashKey(queryKey: QueryKey): string { return JSON.stringify(queryKey, (_, val) => isPlainObject(val) ? Object.keys(val) .sort() .reduce((result, key) => { result[key] = val[key] return result }, {} as any) : val )}function hasObjectPrototype(o: any): boolean { return Object.prototype.toString.call(o) === '[object Object]'}// Copied from: https://github.com/jonschlinkert/is-plain-objectexport function isPlainObject(o: any): o is Object { if (!hasObjectPrototype(o)) { return false } // If has no constructor const ctor = o.constructor if (ctor === undefined) { return true } // If has modified prototype const prot = ctor.prototype if (!hasObjectPrototype(prot)) { return false } // If constructor does not have an Object-specific method if (!prot.hasOwnProperty('isPrototypeOf')) { return false } // Most likely a plain Object return true}export function isPlainArray(value: unknown) { return Array.isArray(value) && value.length === Object.keys(value).length}
Security notes
Remember that this is a simple cache and either needs user id validation prior and caching with the user id or a more complex cache key generation.