@origins-digital/cacheable
v2.2.0
Published
cacheable package
Downloads
417
Readme
@origins-digital/cacheable
A powerful caching package for NestJS applications that provides decorators for Redis-based caching with automatic cache invalidation and key management.
Installation
npm install @origins-digital/cacheableFeatures
- Redis-based caching with decorators
- Automatic cache key generation
- Cache invalidation support
- TTL (Time To Live) configuration
- Raw and formatted cache responses
- Context-aware caching
- Cache hit/miss tracking
- Automatic cache status headers
- Response transformation
Usage
Basic Setup
First, configure the Redis connection and response transformation in your module:
import { Module } from '@nestjs/common';
import { CacheableModule } from '@origins-digital/cacheable';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ResponseTransformInterceptor } from '@origins-digital/cacheable';
@Module({
imports: [
CacheableModule.forRoot({
host: 'localhost',
port: 6379,
// Optional Redis configuration
// password: 'your-password',
// db: 0,
}),
],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: ResponseTransformInterceptor,
},
],
})
export class AppModule {}Using ResponseTransformInterceptor
The ResponseTransformInterceptor automatically transforms responses and adds cache headers:
import { Controller, Get } from '@nestjs/common';
import { Cacheable } from '@origins-digital/cacheable';
import { GenericOutput } from '@origins-digital/cacheable';
@Controller('users')
export class UserController {
@Get(':id')
@Cacheable({
key: 'user:{id}',
raw: false,
})
async getUser(@Param('id') id: string) {
const user = await this.userService.findById(id);
return new GenericOutput(user);
}
@Get()
@Cacheable({
key: 'users:active',
ttlSeconds: 300,
raw: false,
})
async getActiveUsers() {
const users = await this.userService.findActive();
return new GenericOutput(users);
}
}With the interceptor in place, responses will be automatically transformed:
// Example response for GET /users/123
{
"data": {
"id": "123",
"name": "John Doe",
"email": "[email protected]"
},
"metadata": {
"cache": "hit" // or "miss", "set", "no-cache"
}
}
// Example response for GET /users
{
"data": [
{
"id": "123",
"name": "John Doe",
"email": "[email protected]"
},
{
"id": "124",
"name": "Jane Doe",
"email": "[email protected]"
}
],
"metadata": {
"cache": "miss",
"count": 2
}
}The interceptor will also add the X-Redis-Cache header to all responses.
Using Cacheable Decorator
The @Cacheable decorator checks for cached results and sets cache if not found:
import { Injectable } from '@nestjs/common';
import { Cacheable } from '@origins-digital/cacheable';
@Injectable()
export class UserService {
@Cacheable({
key: 'user:{id}',
ttlSeconds: 3600, // Cache for 1 hour
})
async getUser(id: string) {
// This will only execute if the cache miss
return this.userRepository.findById(id);
}
}Using CacheGet Decorator
The @CacheGet decorator only checks for cached results without setting cache:
import { Injectable } from '@nestjs/common';
import { CacheGet } from '@origins-digital/cacheable';
@Injectable()
export class ProductService {
@CacheGet({
key: 'product:{id}',
})
async getProduct(id: string) {
// This will execute on cache miss
return this.productRepository.findById(id);
}
}Using CacheClear Decorator
The @CacheClear decorator invalidates cache entries:
import { Injectable } from '@nestjs/common';
import { CacheClear } from '@origins-digital/cacheable';
@Injectable()
export class UserService {
@CacheClear({
key: 'user:{id}',
})
async updateUser(id: string, data: any) {
// This will clear the cache for this user
return this.userRepository.update(id, data);
}
}Cache Options
All decorators accept options for fine-tuning cache behavior:
interface CacheOptions {
key?: string; // Cache key pattern
ttlSeconds?: number; // Time to live in seconds
excludeContext?: boolean; // Exclude method context from key
forceOne?: boolean; // Force single item from array
raw?: boolean; // Return raw data without metadata
}Cache Response Format
By default, cache responses include metadata:
interface CacheResult {
$fromCache: 'hit' | 'miss' | 'set';
$responseBody: any;
}Using GenericOutput
To get a properly typed response, you can use the GenericOutput utility:
import { Injectable } from '@nestjs/common';
import { Cacheable, GenericOutput } from '@origins-digital/cacheable';
interface User {
id: string;
name: string;
email: string;
}
@Injectable()
export class UserService {
@Cacheable({
key: 'user:{id}',
ttlSeconds: 3600,
})
async getUser(id: string): Promise<GenericOutput<User>> {
const user = await this.userRepository.findById(id);
}
}
// Usage
const result = await userService.getUser('123');
const userData = GenericOutput.data(user); // Get the actual user data
const isFromCache = GenericOutput.fromCache(user); // Check if data came from cache
const isCacheable = GenericOutput.cacheable(user); // Check the cache status: hit, miss, no-cache, setRaw vs Non-Raw Usage Patterns
The raw option should be used differently depending on where the decorator is applied:
Service Level (raw: true)
When using @Cacheable at the service level, set raw: true to get the data directly:
@Injectable()
export class UserService {
@Cacheable({
key: 'user:{id}',
raw: true, // Get data directly at service level
})
async getUser(id: string): Promise<User> {
return this.userRepository.findById(id);
}
}Controller Level (raw: false)
When using @Cacheable at the controller level, keep raw: false to enable cache status headers:
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
@Cacheable({
key: 'user:{id}',
raw: false, // Keep metadata for headers
})
async getUser(@Param('id') id: string): Promise<CacheResult<User>> {
return this.userService.getUser(id);
}
}The cache status will be automatically added to the response headers:
X-Redis-Cache: hit- Data was found in cacheX-Redis-Cache: miss- Data was not in cacheX-Redis-Cache: set- Data was set in cacheX-Redis-Cache: no-cache- Cache was not used
Advanced Examples
Using forceOne and raw options
import { Injectable } from '@nestjs/common';
import { Cacheable } from '@origins-digital/cacheable';
@Injectable()
export class UserService {
// This will return a single user from cache or database
@Cacheable({
key: 'user:{id}',
forceOne: true, // If the result is an array, return only the first item
raw: true, // Return only the data without metadata
})
async getUser(id: string): Promise<User> {
return this.userRepository.findById(id);
}
// This will return a list of users with metadata
@Cacheable({
key: 'users:active',
ttlSeconds: 300,
})
async getActiveUsers(): Promise<CacheResult<User[]>> {
return this.userRepository.findActive();
}
}
// Usage
const user = await userService.getUser('123'); // Returns User directly
const activeUsers = await userService.getActiveUsers(); // Returns CacheResult<User[]>Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
