2.0.3 • Published 10 months ago

@origins-digital/cacheable v2.0.3

Weekly downloads
-
License
MIT
Repository
-
Last release
10 months ago

@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/cacheable

Features

  • 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": "john@example.com"
  },
  "metadata": {
    "cache": "hit" // or "miss", "set", "no-cache"
  }
}

// Example response for GET /users
{
  "data": [
    {
      "id": "123",
      "name": "John Doe",
      "email": "john@example.com"
    },
    {
      "id": "124",
      "name": "Jane Doe",
      "email": "jane@example.com"
    }
  ],
  "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, set

Raw 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 cache
  • X-Redis-Cache: miss - Data was not in cache
  • X-Redis-Cache: set - Data was set in cache
  • X-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.

2.0.3

10 months ago

2.0.2

10 months ago

2.0.1

10 months ago

2.0.0

11 months ago

1.0.9

11 months ago

1.0.8

11 months ago

1.0.7

12 months ago

1.0.6

12 months ago

1.0.5

12 months ago

1.0.4

12 months ago

1.0.3

12 months ago