Skip to content

Instantly share code, notes, and snippets.

@Silent-Watcher
Created May 29, 2025 13:32
Show Gist options
  • Save Silent-Watcher/4596f7faa5aeb3b3ef8851064b70e394 to your computer and use it in GitHub Desktop.
Save Silent-Watcher/4596f7faa5aeb3b3ef8851064b70e394 to your computer and use it in GitHub Desktop.
Express.js+Redis Cache middleware
import { unwrap } from "#app/config/db/global";
import { rawRedis, redis } from "#app/config/db/redis/redis.config";
import type { Request } from "express";
import type { RedisKey } from "ioredis";
import hash from "object-hash";
import type { RedisSetOptions } from "#app/config/db/redis/types";
import type { CommandResult } from "#app/config/db/global";
import { logger } from "#app/common/utils/logger.util";
function buildRedisSetArgs(options: RedisSetOptions): (string | number)[] {
const args: (string | number)[] = [];
if (options.EX !== undefined) args.push("EX", options.EX);
if (options.PX !== undefined) args.push("PX", options.PX);
if (options.EXAT !== undefined) args.push("EXAT", options.EXAT);
if (options.PXAT !== undefined) args.push("PXAT", options.PXAT);
if (options.NX) args.push("NX");
if (options.XX) args.push("XX");
if (options.KEEPTTL) args.push("KEEPTTL");
if (options.GET) args.push("GET");
return args;
}
export function requestToKey(req: Request): string {
const reqDataToHash = {
...(req?.body ? { body: req.body } : {}),
...(req?.query ? { query: req.query } : {}),
user: req?.user?._id.toString("hex"),
};
return `${req.originalUrl}@${hash(reqDataToHash)}`;
}
export function isRedisWorking() {
return rawRedis().status === "ready";
}
export async function writeData(
key: RedisKey,
data: string | Buffer | number,
options?: RedisSetOptions,
) {
if (!isRedisWorking()) return;
try {
const args = [
key,
data,
...(options ? buildRedisSetArgs(options) : []),
];
return unwrap(await redis.fire("set", ...args));
} catch (error) {
logger.error(`Redis write error: ${error}`);
}
}
export async function readData(key: RedisKey): Promise<unknown | undefined> {
if (!isRedisWorking()) return;
try {
const result = unwrap(
await redis.fire("get", key) as CommandResult<unknown>,
);
return result ?? undefined;
} catch (error) {
logger.error(`Redis read error: ${error}`);
return;
}
}
import { httpStatus } from "#app/common/helpers/httpstatus";
import {
isRedisWorking,
readData,
requestToKey,
writeData,
} from "#app/common/helpers/redis";
import type { RedisSetOptions } from "#app/config/db/redis/types";
import type { NextFunction, Request, Response } from "express";
import asyncHandler from "express-async-handler";
export function cache(options: RedisSetOptions = { EX: 3000 }) {
return asyncHandler(
async (
req: Request,
res: Response,
next: NextFunction,
): Promise<void> => {
try {
if (!isRedisWorking) {
next();
return;
}
const key = requestToKey(req);
const cachedData = await readData(key);
if (cachedData) {
try {
res.send({
data: JSON.parse(cachedData as string),
});
} catch (error) {
res.send({
data: cachedData,
});
}
} else {
const oldSend = res.send;
res.send = (data) => {
res.send = oldSend;
if (res.statusCode.toString().startsWith("2")) {
writeData(key, JSON.stringify(data), options)
.then();
}
return res.send({ data });
};
next();
}
} catch (error) {
next(error);
}
},
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment