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>
This commit is contained in:
100
backend/middleware/cache.js
Normal file
100
backend/middleware/cache.js
Normal file
@@ -0,0 +1,100 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user