@mondart/nestjs-common-module-caching
v3.2.3
Published
Caching module for NestJS.
Readme
@mondart/nestjs-common-module-caching
Redis-backed caching for NestJS: a CachingService for direct key/value
access, an HTTP response-caching interceptor, and a DistributedLockService
for coordinating work across multiple instances of the same service (e.g. so
only one replica runs a given cron tick).
Registration
import { CachingModule } from '@mondart/nestjs-common-module-caching';
@Module({
imports: [
CachingModule.register({
host: 'localhost',
port: 6379,
password: 'secret',
database: 0,
namespace: 'my-service',
ttl: 60_000, // default TTL in ms
}),
],
})
export class AppModule {}Or asynchronously:
CachingModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
password: config.get('REDIS_PASSWORD'),
}),
});CachingModule is @Global(), so CachingService and
DistributedLockService are available for injection anywhere without
re-importing the module.
CachingService
constructor(private readonly cachingService: CachingService) {}
await this.cachingService.set('user:42', user, 60); // ttlSeconds
const user = await this.cachingService.get<User>('user:42');
await this.cachingService.setMany([{ key: 'a', value: 1, ttlSeconds: 30 }]);
const [a, b] = await this.cachingService.getMany<number>(['a', 'b']);
await this.cachingService.has('user:42');
await this.cachingService.ttl('user:42');
await this.cachingService.del('user:42');
await this.cachingService.delMany(['a', 'b']);
await this.cachingService.clear();Caching HTTP responses
CachingModule registers CachingInterceptor globally, but it only caches a
GET route when the handler (or controller) is explicitly opted in — nothing
is cached by default.
import {
UseCachingInterceptorDecorator,
SkipCachingInterceptorDecorator,
} from '@mondart/nestjs-common-module-caching';
@UseCachingInterceptorDecorator({ ttl: 30_000 })
@Get()
findAll() { ... }
@SkipCachingInterceptorDecorator()
@Get('live')
findLive() { ... } // never cached, even if the controller opts inkey/ttl accept the same static value or factory function types as
@nestjs/cache-manager's own CacheKey/CacheTTL. When no key is given,
requests are cached per ControllerName_methodName:<url+query hash>.
Distributed locking
Use this to stop the same piece of work (typically a @Cron() handler)
running concurrently across multiple instances of a service.
Programmatic: DistributedLockService.runExclusive
constructor(private readonly lockService: DistributedLockService) {}
@Cron('*/5 * * * *')
async syncJob() {
await this.lockService.runExclusive(
'sync-job',
async ({ signal }) => {
for (const item of items) {
if (signal.aborted) break; // lock could no longer be renewed
await process(item);
}
},
{ ttlMs: 30_000, autoExtend: true }, // autoExtend defaults to true
);
}- Returns
undefinedwithout invoking the callback when another instance already holds the lock — the expected outcome on replicas that lose the race for a given tick. - With
autoExtend(defaulttrue), the lock is renewed on an interval (ttlMs / 3by default, orextensionIntervalMs) for as long as the callback runs. If renewal ever falls behind the lock's real TTL, the callback'ssignalis aborted so long-running work can checksignal.abortedand stop early instead of continuing to run without exclusivity. acquire,release, andextendare also available individually for manual lock management.
Declarative: @DistributedLockDecorator
import { DistributedLockDecorator } from '@mondart/nestjs-common-module-caching';
@Cron('*/5 * * * *')
@DistributedLockDecorator({ key: 'sync-job', ttlMs: 30_000 })
async syncJob() { ... }key can also be a function of the method's arguments, e.g.
key: (tenantId: string) => \sync-job:${tenantId}`to scope the lock per
call. This works on methods that Nest itself never routes through an
interceptor (like@Cron()handlers) becauseDistributedLockExplorer`
wraps decorated methods directly at application bootstrap, rather than
relying on the request pipeline.
Both approaches share the same DistributedLockService under the hood, so
lock keys are namespaced the same way (CachingModuleOptions.namespace,
if set) regardless of which style you use.
