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:
William Valentin
2025-11-03 13:25:50 -08:00
parent 43c2e76070
commit ae77e30ffb
10 changed files with 301 additions and 1 deletions

43
backend/routes/cache.js Normal file
View File

@@ -0,0 +1,43 @@
const express = require("express");
const auth = require("../middleware/auth");
const { asyncHandler } = require("../middleware/errorHandler");
const { getCacheStats, clearCache } = require("../middleware/cache");
const router = express.Router();
/**
* GET /api/cache/stats
* Get cache statistics
* @access Private (authenticated users)
*/
router.get(
"/stats",
auth,
asyncHandler(async (req, res) => {
const stats = getCacheStats();
res.json({
status: "operational",
...stats,
timestamp: new Date().toISOString()
});
})
);
/**
* DELETE /api/cache
* Clear all cache
* @access Private (authenticated users - in production should be admin only)
*/
router.delete(
"/",
auth,
asyncHandler(async (req, res) => {
clearCache();
res.json({
msg: "Cache cleared successfully",
timestamp: new Date().toISOString()
});
})
);
module.exports = router;

View File

@@ -9,6 +9,7 @@ const {
} = require("../middleware/validators/eventValidator");
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
const couchdbService = require("../services/couchdbService");
const { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache");
const router = express.Router();
@@ -16,6 +17,7 @@ const router = express.Router();
router.get(
"/",
paginate,
getCacheMiddleware(120), // Cache for 2 minutes
asyncHandler(async (req, res) => {
const { page, limit } = req.pagination;
@@ -50,6 +52,9 @@ router.post(
location,
});
// Invalidate events cache
invalidateCacheByPattern('/api/events');
res.json(event);
}),
);

View File

@@ -10,6 +10,7 @@ const {
const { upload, handleUploadError } = require("../middleware/upload");
const { uploadImage, deleteImage } = require("../config/cloudinary");
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
const { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache");
const router = express.Router();
@@ -17,6 +18,7 @@ const router = express.Router();
router.get(
"/",
paginate,
getCacheMiddleware(60), // Cache for 1 minute
asyncHandler(async (req, res) => {
const { skip, limit, page } = req.pagination;
@@ -58,6 +60,9 @@ router.post(
const post = await Post.create(postData);
// Invalidate posts cache
invalidateCacheByPattern('/api/posts');
res.json({
post,
pointsAwarded: 5, // Standard post creation points

View File

@@ -7,6 +7,7 @@ const {
rewardIdValidation,
} = require("../middleware/validators/rewardValidator");
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
const { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache");
const router = express.Router();
@@ -14,6 +15,7 @@ const router = express.Router();
router.get(
"/",
paginate,
getCacheMiddleware(600), // Cache for 10 minutes
asyncHandler(async (req, res) => {
const { page, limit } = req.pagination;
@@ -37,6 +39,9 @@ router.post(
isPremium,
});
// Invalidate rewards cache
invalidateCacheByPattern('/api/rewards');
res.json(reward);
}),
);

View File

@@ -8,12 +8,14 @@ const {
createStreetValidation,
streetIdValidation,
} = require("../middleware/validators/streetValidator");
const { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache");
const router = express.Router();
// Get all streets (with pagination and filtering)
router.get(
"/",
getCacheMiddleware(300), // Cache for 5 minutes
asyncHandler(async (req, res) => {
const { buildPaginatedResponse } = require("../middleware/pagination");
@@ -81,6 +83,7 @@ router.get(
router.get(
"/:id",
streetIdValidation,
getCacheMiddleware(300), // Cache for 5 minutes
asyncHandler(async (req, res) => {
const street = await Street.findById(req.params.id);
if (!street) {
@@ -109,6 +112,9 @@ router.post(
location,
});
// Invalidate streets cache
invalidateCacheByPattern('/api/streets');
res.json(street);
}),
);
@@ -156,6 +162,9 @@ router.put(
user.stats.streetsAdopted = user.adoptedStreets.length;
await user.save();
// Invalidate streets cache
invalidateCacheByPattern('/api/streets');
// Award points for street adoption using CouchDB service
const updatedUser = await couchdbService.updateUserPoints(
req.user.id,