Files
adopt-a-street/backend/middleware/cache.js
William Valentin ae77e30ffb feat: implement API response caching with node-cache
- Add in-memory cache middleware with configurable TTL
- Cache GET endpoints: streets (5min), events (2min), posts (1min), rewards (10min)
- Automatic cache invalidation on POST/PUT/DELETE operations
- Add cache statistics endpoint (GET /api/cache/stats)
- Add cache management endpoint (DELETE /api/cache)
- Cache hit rate tracking and monitoring
- Pattern-based cache invalidation
- Optimized for Raspberry Pi deployment (lightweight in-memory)

🤖 Generated with Claude

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 13:25:50 -08:00

101 lines
2.5 KiB
JavaScript

const NodeCache = require('node-cache');
const logger = require('../utils/logger');
// Initialize cache with default TTL of 5 minutes and check period of 2 minutes
const cache = new NodeCache({ stdTTL: 300, checkperiod: 120 });
/**
* Cache middleware factory
* @param {number} ttlSeconds - Time to live in seconds for cached responses
* @returns {Function} Express middleware function
*/
const getCacheMiddleware = (ttlSeconds) => (req, res, next) => {
const key = req.originalUrl;
const cachedResponse = cache.get(key);
if (cachedResponse) {
logger.info(`Cache hit for key: ${key}`);
return res.json(cachedResponse);
}
logger.info(`Cache miss for key: ${key}`);
// Store the original res.json method
const originalJson = res.json.bind(res);
// Override res.json to cache the response
res.json = (body) => {
cache.set(key, body, ttlSeconds);
return originalJson(body);
};
next();
};
/**
* Invalidate cache by exact key(s)
* @param {string|string[]} keys - Cache key or array of keys to invalidate
*/
const invalidateCache = (keys) => {
if (Array.isArray(keys)) {
keys.forEach(key => {
logger.info(`Invalidating cache for key: ${key}`);
cache.del(key);
});
} else {
logger.info(`Invalidating cache for key: ${keys}`);
cache.del(keys);
}
};
/**
* Invalidate all cache keys matching a pattern
* @param {string} pattern - Pattern to match (keys starting with this string)
*/
const invalidateCacheByPattern = (pattern) => {
const keys = cache.keys();
const keysToDelete = keys.filter(key => key.startsWith(pattern));
if (keysToDelete.length > 0) {
logger.info(`Invalidating cache by pattern: ${pattern}, deleted ${keysToDelete.length} keys`);
cache.del(keysToDelete);
} else {
logger.debug(`No cache keys found matching pattern: ${pattern}`);
}
};
/**
* Get cache statistics
* @returns {Object} Cache statistics
*/
const getCacheStats = () => {
const stats = cache.getStats();
const keys = cache.keys();
return {
keys: keys.length,
hits: stats.hits,
misses: stats.misses,
hitRate: stats.hits > 0 ? (stats.hits / (stats.hits + stats.misses) * 100).toFixed(2) + '%' : '0%',
ksize: stats.ksize,
vsize: stats.vsize
};
};
/**
* Clear all cache
*/
const clearCache = () => {
const keyCount = cache.keys().length;
cache.flushAll();
logger.info(`Cleared all cache (${keyCount} keys)`);
};
module.exports = {
getCacheMiddleware,
invalidateCache,
invalidateCacheByPattern,
getCacheStats,
clearCache
};