Response Caching
Response Caching
Section titled “Response Caching”Learn how to implement response caching in your Veloce-TS applications to reduce latency and database load.
Table of Contents
Section titled “Table of Contents”- Overview
- Quick Start
- Cache Stores
- Decorators
- Middleware
- Cache Keys
- TTL Configuration
- Cache Invalidation
- Best Practices
Overview
Section titled “Overview”Veloce-TS provides a powerful caching system that allows you to cache expensive operations and reduce response times. The system supports:
- In-Memory Store - Fast caching for single-instance applications
- Redis Store - Distributed caching for multi-instance deployments
- TTL Support - Flexible time-to-live configuration
- Pattern-Based Invalidation - Invalidate related caches efficiently
- Cache Headers - Automatic
X-Cache: HIT/MISSheaders
Quick Start
Section titled “Quick Start”1. Basic Setup
Section titled “1. Basic Setup”import { VeloceTS, CacheManager, MemoryCacheStore } from 'veloce-ts';
const app = new VeloceTS();
// Configure cache storeconst cacheStore = new MemoryCacheStore({ maxSize: 1000, // Maximum entries cleanupInterval: 60000 // Clean expired entries every 60s});
CacheManager.setDefaultStore(cacheStore);2. Cache a Route
Section titled “2. Cache a Route”import { Controller, Get, Cache } from 'veloce-ts';
@Controller('/products')class ProductController { @Get('/') @Cache({ ttl: '5m' }) // Cache for 5 minutes async getAllProducts() { return await db.products.findAll(); }}Cache Stores
Section titled “Cache Stores”In-Memory Store
Section titled “In-Memory Store”Perfect for single-instance applications or development:
import { MemoryCacheStore, CacheManager } from 'veloce-ts';
const store = new MemoryCacheStore({ maxSize: 1000, // Max entries (LRU eviction) cleanupInterval: 60000 // Cleanup interval in ms});
CacheManager.setDefaultStore(store);Features:
- Fast (< 1ms access time)
- LRU (Least Recently Used) eviction
- Automatic cleanup of expired entries
- No external dependencies
Redis Store
Section titled “Redis Store”For distributed applications with multiple instances:
import { createRedisCacheStore, CacheManager } from 'veloce-ts';import { createClient } from 'redis';
// Using redis packageconst client = createClient({ url: 'redis://localhost:6379' });await client.connect();
const store = createRedisCacheStore(client, { prefix: 'myapp:cache:' // Key prefix for namespacing});
CacheManager.setDefaultStore(store);// Using ioredis packageimport Redis from 'ioredis';
const client = new Redis('redis://localhost:6379');const store = createRedisCacheStore(client);Features:
- Distributed caching across instances
- Persistent cache (survives restarts)
- ~2-5ms access time
- Supports clustering
Decorators
Section titled “Decorators”@Cache()
Section titled “@Cache()”Cache route responses automatically:
@Controller('/api')class ApiController { // Simple TTL @Get('/data') @Cache({ ttl: 300 }) // 5 minutes in seconds async getData() { return await fetchExpensiveData(); }
// String format TTL @Get('/products') @Cache({ ttl: '5m' }) // Supports: '5s', '5m', '5h', '5d' async getProducts() { return await db.products.findAll(); }
// Custom cache key @Get('/user/:id') @Cache({ ttl: '10m', key: 'user:{id}' }) async getUser(@Param('id') id: string) { return await db.users.findById(id); }
// Cache with query params @Get('/search') @Cache({ ttl: '2m', includeQuery: true // Different cache for each query }) async search(@Query() query: any) { return await db.products.search(query); }
// Conditional caching @Get('/items') @Cache({ ttl: '5m', condition: (result) => result.length > 0 // Only cache if has results }) async getItems() { return await db.items.findAll(); }}Options:
interface CacheOptions { ttl: number | string; // Time to live key?: string; // Custom key (supports {param} placeholders) prefix?: string; // Key prefix includeQuery?: boolean; // Include query params in key varyByHeaders?: string[]; // Vary by specific headers condition?: (result) => boolean; // Cache condition store?: CacheStore; // Custom store}@CacheInvalidate()
Section titled “@CacheInvalidate()”Invalidate cache after mutations:
@Controller('/products')class ProductController { @Get('/') @Cache({ ttl: '5m', key: 'products:list' }) async list() { return await db.products.findAll(); }
@Post('/') @CacheInvalidate('products:*') // Clear all product caches async create(@Body() data: CreateProductDTO) { return await db.products.create(data); }
@Put('/:id') @CacheInvalidate(['product:{id}', 'products:*']) // Multiple patterns async update(@Param('id') id: string, @Body() data: UpdateProductDTO) { return await db.products.update(id, data); }
@Delete('/:id') @CacheInvalidate([ 'product:{id}', 'products:*', 'featured:*' ]) async delete(@Param('id') id: string) { await db.products.delete(id); return { success: true }; }}Middleware
Section titled “Middleware”For functional API routes:
Cache Middleware
Section titled “Cache Middleware”import { createCacheMiddleware } from 'veloce-ts';
app.get('/products', { middleware: [createCacheMiddleware({ ttl: '5m' })], handler: async () => { return await db.products.findAll(); }});
// With custom keyapp.get('/user/:id/posts', { middleware: [createCacheMiddleware({ ttl: 300, key: 'user:{id}:posts' })], handler: async (c) => { const id = c.req.param('id'); return await db.posts.findByUser(id); }});Cache Invalidation Middleware
Section titled “Cache Invalidation Middleware”import { createCacheInvalidationMiddleware } from 'veloce-ts';
app.post('/products', { middleware: [createCacheInvalidationMiddleware('products:*')], handler: async (c) => { const body = await c.req.json(); return await db.products.create(body); }});
// Multiple patternsapp.put('/products/:id', { middleware: [createCacheInvalidationMiddleware([ 'product:{id}', 'products:*' ])], handler: async (c) => { const id = c.req.param('id'); const body = await c.req.json(); return await db.products.update(id, body); }});Cache Keys
Section titled “Cache Keys”Automatic Generation
Section titled “Automatic Generation”Cache keys are automatically generated from:
- HTTP method
- Route path
- Route parameters
- Query parameters (if
includeQuery: true) - Headers (if
varyByHeadersspecified)
Example:
// GET /products/123?sort=name// Default key: "get:/products/:id:{"id":"123"}"// With includeQuery: "get:/products/:id:{"id":"123"}:{"sort":"name"}"Custom Keys with Placeholders
Section titled “Custom Keys with Placeholders”Use placeholders to create semantic keys:
@Get('/products/:id')@Cache({ ttl: '5m', key: 'product:{id}' })async getProduct(@Param('id') id: string) { return await db.products.findById(id);}// Key becomes: "product:123"
@Get('/user/:userId/posts/:postId')@Cache({ ttl: '5m', key: 'user:{userId}:post:{postId}' })async getPost(@Param('userId') userId: string, @Param('postId') postId: string) { return await db.posts.find(userId, postId);}// Key becomes: "user:456:post:789"Key Prefixes
Section titled “Key Prefixes”Namespace your cache keys:
@Cache({ ttl: '5m', prefix: 'api:v1', key: 'products:list'})// Final key: "api:v1:products:list"TTL Configuration
Section titled “TTL Configuration”Formats
Section titled “Formats”// Seconds (number)@Cache({ ttl: 300 }) // 5 minutes
// String format@Cache({ ttl: '5s' }) // 5 seconds@Cache({ ttl: '5m' }) // 5 minutes@Cache({ ttl: '5h' }) // 5 hours@Cache({ ttl: '5d' }) // 5 daysRecommendations
Section titled “Recommendations”| Data Type | TTL | Example |
|---|---|---|
| Static content | 1d - 7d | Help pages, terms |
| Product catalog | 5m - 1h | Products, categories |
| User data | 1m - 5m | User profile |
| Search results | 30s - 2m | Search queries |
| Real-time data | 10s - 30s | Stock prices |
Cache Invalidation
Section titled “Cache Invalidation”Pattern Matching
Section titled “Pattern Matching”Use wildcards to invalidate related caches:
// Clear all product cachesawait CacheManager.invalidate('products:*');
// Clear all user cachesawait CacheManager.invalidate('user:*');
// Clear specific patternawait CacheManager.invalidate('user:123:*');Manual Invalidation
Section titled “Manual Invalidation”import { CacheManager, invalidateCache, deleteCache, clearCache } from 'veloce-ts';
// Invalidate by patternawait CacheManager.invalidate('products:*');await invalidateCache('products:*');
// Delete specific keyawait CacheManager.delete('product:123');await deleteCache('product:123');
// Clear all cacheawait CacheManager.clear();await clearCache();Programmatic Caching
Section titled “Programmatic Caching”import { getCache, setCache } from 'veloce-ts';
// Get from cacheconst data = await getCache<Product[]>('products:list');
// Set in cacheawait setCache('products:list', products, '5m');
// Delete from cacheawait deleteCache('products:list');Cache Headers
Section titled “Cache Headers”Responses automatically include cache status:
HTTP/1.1 200 OKX-Cache: HITX-Request-ID: abc-123-def-456Content-Type: application/jsonor
HTTP/1.1 200 OKX-Cache: MISSX-Request-ID: abc-123-def-456Content-Type: application/jsonBest Practices
Section titled “Best Practices”1. Cache Read-Heavy Operations
Section titled “1. Cache Read-Heavy Operations”✅ Good candidates for caching:
- Product catalogs
- User profiles (when not frequently updated)
- Search results
- Aggregated data
- External API responses
❌ Don’t cache:
- User-specific sensitive data
- Real-time data (unless very short TTL)
- Data that changes frequently
2. Use Appropriate TTL
Section titled “2. Use Appropriate TTL”// Static data - Long TTL@Cache({ ttl: '1d' })async getTermsOfService() { ... }
// Dynamic data - Short TTL@Cache({ ttl: '30s' })async getCurrentPrice() { ... }
// User data - Medium TTL@Cache({ ttl: '5m' })async getUserProfile() { ... }3. Invalidate on Mutations
Section titled “3. Invalidate on Mutations”Always invalidate related caches after updates:
@Post('/')@CacheInvalidate('products:*')async createProduct() { ... }
@Put('/:id')@CacheInvalidate(['product:{id}', 'products:*'])async updateProduct() { ... }4. Use Pattern Matching Wisely
Section titled “4. Use Pattern Matching Wisely”// Good - Specific pattern@CacheInvalidate('products:category:electronics')
// Better - More specific@CacheInvalidate('products:category:electronics:page:*')
// Too broad - May clear unrelated caches@CacheInvalidate('*')5. Monitor Cache Performance
Section titled “5. Monitor Cache Performance”const store = CacheManager.getDefaultStore() as MemoryCacheStore;const stats = store.getStats();console.log({ size: stats.size, maxSize: stats.maxSize, utilization: (stats.size / stats.maxSize) * 100});6. Use Redis for Production
Section titled “6. Use Redis for Production”For multi-instance deployments:
// Development - Use Memory Storeif (process.env.NODE_ENV === 'development') { CacheManager.setDefaultStore(new MemoryCacheStore());}
// Production - Use Redisif (process.env.NODE_ENV === 'production') { const redis = createClient({ url: process.env.REDIS_URL }); await redis.connect(); CacheManager.setDefaultStore(createRedisCacheStore(redis));}Complete Example
Section titled “Complete Example”import { VeloceTS, Controller, Get, Post, Put, Delete, Param, Body, Cache, CacheInvalidate, CacheManager, MemoryCacheStore} from 'veloce-ts';import { z } from 'zod';
// Configure cacheconst cacheStore = new MemoryCacheStore({ maxSize: 1000 });CacheManager.setDefaultStore(cacheStore);
const ProductSchema = z.object({ name: z.string(), price: z.number()});
@Controller('/products')class ProductController { // List - cached for 5 minutes @Get('/') @Cache({ ttl: '5m', key: 'products:list' }) async list() { return await db.products.findAll(); }
// Get by ID - cached for 10 minutes @Get('/:id') @Cache({ ttl: '10m', key: 'product:{id}' }) async getById(@Param('id') id: string) { return await db.products.findById(id); }
// Search - cached by query params @Get('/search') @Cache({ ttl: '2m', includeQuery: true }) async search(@Query() query: any) { return await db.products.search(query); }
// Create - invalidates list cache @Post('/') @CacheInvalidate('products:*') async create(@Body(ProductSchema) data: z.infer<typeof ProductSchema>) { return await db.products.create(data); }
// Update - invalidates specific product and list @Put('/:id') @CacheInvalidate(['product:{id}', 'products:*']) async update( @Param('id') id: string, @Body(ProductSchema) data: z.infer<typeof ProductSchema> ) { return await db.products.update(id, data); }
// Delete - invalidates all related caches @Delete('/:id') @CacheInvalidate(['product:{id}', 'products:*']) async delete(@Param('id') id: string) { await db.products.delete(id); return { success: true }; }}
const app = new VeloceTS();app.include(ProductController);app.listen(3000);Next Steps
Section titled “Next Steps”- Learn about Request Context for request tracking
- Explore Logging for debugging cache behavior
- Check Performance Best Practices for optimization tips