Caching
KickJS provides @Cacheable and @CacheEvict decorators with a pluggable cache backend. By default, an in-memory Map is used. Swap in Redis, Memcached, or any custom store by implementing the CacheProvider interface.
Quick Start
import { Service, Cacheable, CacheEvict } from '@forinda/kickjs'
@Service()
export class ProductService {
// Cache for 5 minutes
@Cacheable(300, { key: 'products' })
async findAll() {
return db.products.findMany()
}
// Cache individual lookups for 10 minutes
@Cacheable(600, { key: 'product' })
async findById(id: string) {
return db.products.findUnique({ where: { id } })
}
// Evict the products cache when data changes
@CacheEvict('products')
async create(data: any) {
return db.products.create({ data })
}
@CacheEvict('product')
async update(id: string, data: any) {
return db.products.update({ where: { id }, data })
}
}That's it — caching works out of the box with the built-in memory provider.
Custom Cache Provider
Implement the CacheProvider interface to use any backend:
import type { CacheProvider } from '@forinda/kickjs'
export interface CacheProvider {
get(key: string): Promise<any | null>
set(key: string, value: any, ttlMs: number): Promise<void>
del(key: string): Promise<void>
delByPrefix?(prefix: string): Promise<void>
shutdown?(): Promise<void>
}Redis Example
import type { CacheProvider } from '@forinda/kickjs'
import Redis from 'ioredis'
export class RedisCacheProvider implements CacheProvider {
private client: Redis
constructor(url?: string) {
this.client = new Redis(url)
}
async get(key: string): Promise<any | null> {
const val = await this.client.get(key)
return val ? JSON.parse(val) : null
}
async set(key: string, value: any, ttlMs: number): Promise<void> {
await this.client.set(key, JSON.stringify(value), 'PX', ttlMs)
}
async del(key: string): Promise<void> {
await this.client.del(key)
}
async delByPrefix(prefix: string): Promise<void> {
const keys = await this.client.keys(`${prefix}*`)
if (keys.length > 0) {
await this.client.del(...keys)
}
}
async shutdown(): Promise<void> {
await this.client.quit()
}
}Registering Your Provider
Call setCacheProvider() before bootstrapping:
import { setCacheProvider } from '@forinda/kickjs'
import { RedisCacheProvider } from './redis-cache'
// Use Redis for all @Cacheable/@CacheEvict
setCacheProvider(new RedisCacheProvider('redis://localhost:6379'))
bootstrap({ modules: [...], adapters: [...] })Built-in Providers
MemoryCacheProvider (default)
In-memory Map-based cache. Works out of the box, no configuration needed.
- Suitable for development and single-instance deployments
- Data is lost on restart
- No cross-process sharing
import { setCacheProvider, MemoryCacheProvider } from '@forinda/kickjs'
// This is the default — you only need this if you want to reset
setCacheProvider(new MemoryCacheProvider())How Cache Keys Work
The cache key is constructed as {prefix}:{JSON.stringify(args)}:
- prefix defaults to
{method}:{ClassName}(for examplefindAll:UserService), or is exactlyoptions.keywhen you set one - args are the method arguments, serialized to JSON
This means findById('abc') and findById('xyz') get separate cache entries, and UserService.findAll() and PostService.findAll() don't share one. The method name stays first, so @CacheEvict('findAll') still clears default-keyed findAll entries — for every class that has one. An explicit options.key is used as given, so two classes that set the same key share entries on purpose.
Simultaneous misses on the same key share one call: if ten requests miss findAll:UserService:[] at once, the method runs once and all ten get its result. A failed call isn't cached, so the next one retries.
@CacheEvict
@CacheEvict(key) deletes all cache entries whose key starts with the given prefix. This is called after the decorated method completes successfully.
// Caches under "users:..." prefix
@Cacheable(300, { key: 'users' })
async listUsers() { ... }
// Evicts all "users:..." entries
@CacheEvict('users')
async createUser(data: any) { ... }For this to work across methods, both decorators must use the same key prefix, and your CacheProvider must implement delByPrefix.
API Reference
@Cacheable(ttl?, options?)
| Param | Type | Default | Description |
|---|---|---|---|
ttl | number | 60 | Time-to-live in seconds |
options.key | string | {method}:{ClassName} | Cache key prefix |
@CacheEvict(key)
| Param | Type | Description |
|---|---|---|
key | string | Cache key prefix to evict |
setCacheProvider(provider)
Set the global cache provider. Call before bootstrap().
getCacheProvider()
Returns the currently active CacheProvider instance.
MemoryCacheProvider
Built-in provider using an in-memory Map. This is the default.