express-redis-simple-cache
Version:
A simple and effective Redis-based caching middleware for Express.js APIs.
156 lines (153 loc) • 4.8 kB
JavaScript
// src/index.ts
import { createClient } from "redis";
// src/log.ts
var currentLogLevel = "normal";
function setLogLevel(level) {
currentLogLevel = level;
}
function log(message, level = "log") {
if (currentLogLevel === "silent") return;
if (level === "debug" && currentLogLevel !== "debug") return;
console[level](`\u{1F5C4}\uFE0F [Cache] ${message}`);
}
// src/keyGenerator.ts
function generateCacheKey(route, req) {
const { method, route: routePath } = route;
const url = req.originalUrl || req.url;
switch (route.cache?.type) {
case "always":
return `cache:${method}:${routePath}`;
case "per-auth-token":
const authCookie = getAuthCookie(req);
if (!authCookie) {
log("No auth cookie found in request. Cache will not be applied. Please use per-custom-cookie and set your custom cookie name.", "warn");
return false;
}
return `cache:${method}:${routePath}:${authCookie}`;
case "per-request-url":
return `cache:${method}:${req.url}`;
case "per-custom-cookie":
if (route.cache.customCookie) {
const cookies = req.cookies || {};
const customCookieValue = cookies[route.cache.customCookie] || "";
if (!customCookieValue) {
log(`Custom cookie "${route.cache.customCookie}" not found in request. Cache will not be applied.`, "warn");
return false;
}
return `cache:${method}:${routePath}:${customCookieValue}`;
} else {
log("Custom cookie is not defined for per-custom-cookie cache type.", "error");
return false;
}
}
return false;
}
function getAuthCookie(req) {
const commonAuthCookieNames = [
"authToken",
"accessToken",
"refreshToken",
"idToken",
"jwt",
"token",
"sessionToken",
"auth_token",
"access_token",
"refresh_token",
"bearer_token"
];
const commonAuthHeaderNames = [
"Authorization",
"X-Auth-Token",
"X-Access-Token",
"X-Refresh-Token",
"X-ID-Token",
"X-API-key",
"Token",
"Auth-Token"
];
let authCookie = commonAuthCookieNames.map((name) => req.cookies[name]).find((value) => value !== void 0) || null;
if (!authCookie) {
authCookie = commonAuthHeaderNames.map((name) => req.headers[name.toLowerCase()]).find((value) => value !== void 0) || null;
}
return authCookie;
}
// src/index.ts
var DEFAULT_CACHE_TTL = 60;
var redis = null;
async function setupCache(redisClientOptions, logLevel) {
redis = createClient(redisClientOptions);
setLogLevel(logLevel);
redis.on("connect", () => log("Connected to Redis"));
redis.on("error", (err) => log(`Redis client error: ${err.message}`, "error"));
await redis.connect();
return redis;
}
async function stopCache(redis2) {
await redis2.quit();
log("Redis connection closed");
}
function cache(route) {
if (route.cache) {
return cacheMiddleware(route);
} else {
return (req, res, next) => next();
}
}
function cacheMiddleware(route) {
if (!redis || !redis.isReady) {
log("Redis client is not initialized. Please call setupCache first. Cache will not be applied and next function be called.", "error");
return (req, res, next) => next();
}
return async (req, res, next) => {
if (!redis || !redis.isReady) {
return next();
}
const cacheKey = generateCacheKey(route, req);
if (!cacheKey) {
log("Cache key generation failed. Cache will not be applied.", "warn");
return next();
}
const data = await redis.get(cacheKey);
if (!data) {
log(`Cache miss for key: ${cacheKey}`, "debug");
res.sendResponse = res.send;
res.send = async (body) => {
if (!redis || !redis.isReady) {
return next();
}
if (typeof body === "object") {
body = JSON.stringify(body);
}
await redis.set(cacheKey, body, { EX: route.cache?.expire || DEFAULT_CACHE_TTL });
log(`Data cached for key: ${cacheKey}`, "debug");
res.sendResponse(body);
};
const originalJson = res.json.bind(res);
res.json = async (body) => {
if (!redis || !redis.isReady) {
return next();
}
await redis.set(cacheKey, JSON.stringify(body), { EX: route.cache?.expire || DEFAULT_CACHE_TTL });
log(`JSON Data cached for key: ${cacheKey}`, "debug");
return originalJson(body);
};
next();
} else {
log(`Cache hit for key: ${cacheKey}`, "debug");
res.setHeader("X-Cache", "HIT");
const dataStr = data.toString();
if (dataStr.startsWith("{") || dataStr.startsWith("[")) {
res.json(JSON.parse(dataStr));
} else {
res.send(data);
}
}
};
}
export {
cache,
setupCache,
stopCache
};
//# sourceMappingURL=index.js.map