How I Eliminated 90% of My Express Caching Code

Table of contents
- Stop Writing Boring Redis Boilerplate! Meet redease: Caching Made Stupidly Simple.
- What is redease?
- The “Before and After” That Will Make You Question Your Life Choices
- The Old Way: 🤮 (You Probably Have Code That Looks Like This)
- The redease Way: 😎 (Behold, the Glory)
- Key Features You’re Getting for Free
- Conclusion: You’d Be Silly Not To Use This
- Get Started Now:

Stop Writing Boring Redis Boilerplate! Meet redease: Caching Made Stupidly Simple.
Let’s be honest. If you’re building Express APIs, you’ve written this code a hundred times:
Connect to Redis.
Before a request, check if the data is cached.
If it is, parse it and return it.
If it’s not, run the expensive DB query.
After the query, stringify the result and stuff it into Redis with a
SETEX
.Pray you remembered to handle all the error cases.
When data changes, try to remember all the possible cache keys to delete.
Fail at step 7, causing users to see stale data.
Cry a little.
What if I told you you could delete 90% of that code? Enter redease
.
What is redease
?
redease
is an npm package that adds powerful, automatic Redis caching to your Express.js routes. It's built on two simple principles:
cache()
: Decorate a GET route to automatically cache its response.invalidate()
: Decorate a POST/PUT/DELETE route to automatically clear relevant cache.
It handles key generation, serialization, error handling, and connection management for you. You focus on your business logic; it focuses on making your app blazingly fast. ⚡
The “Before and After” That Will Make You Question Your Life Choices
Let’s look at a classic example: a Users API.
The Old Way: 🤮 (You Probably Have Code That Looks Like This)
Typescript
// 🚨 WARNING: Boilerplate Incoming 🚨
import express from "express";
import { pool } from "../db";
import { redisClient } from "../redis"; // Your custom client
const router = express.Router();// Get all users - THE HARD WAY
router.get("/", async (req, res) => {
const cacheKey = `users:all`; // Manually invent a key try {
// 1. Check Cache
const cachedData = await redisClient.get(cacheKey);
if (cachedData) {
return res.json({ ...JSON.parse(cachedData), cached: true });
} // 2. Cache Miss? Query DB.
console.log("📊 Cache MISS - Fetching from DB...");
const { rows } = await pool.query("SELECT * FROM users");
const dataToSend = { data: rows, cached: false, timestamp: new Date().toISOString() }; // 3. Cache for next time
await redisClient.setex(cacheKey, 300, JSON.stringify(dataToSend)); // More manual work // 4. Send response
res.json(dataToSend);
} catch (error) {
// 5. Hope you remembered to handle errors!
console.error("Caching error:", error);
res.status(500).send("Server oopsie");
}
});
// Create a user - THE HARD WAY
router.post("/", async (req, res) => {
const { name, email } = req.body;
const { rows } = await pool.query("INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", [name, email]); // 🤦 How do you invalidate the cache?
// You have to remember EVERY key that might have user data.
try {
await redisClient.del(`users:all`); // Did you get them all?
// await redisClient.del('users:page:1'); // Did you remember pagination?
// await redisClient.del(`user:${rows[0].id}`); // What about the single-user cache?
console.log("❓ Hopefully invalidated the right stuff...");
} catch (error) {
// 😬 Do you even handle this error?
} res.status(201).json(rows[0]);
});
See the problems?
Repetitive Code: This same
try/cache/get/setex
logic is repeated in every route.Fragile Invalidation: You will forget to clear a key. It’s not a matter of if, but when.
Tight Coupling: Your beautiful route logic is polluted with caching concerns.
Error Handling: It’s an afterthought.
The redease
Way: 😎 (Behold, the Glory)
redease
offers two styles: Automatic, for quick and easy wins, and Manual, for when you need precise control. Both are infinitely better than rolling your own.
Style 1: Automatic Key Generation (The Easy Win)
This is the fastest way to get started. You don’t think about keys at all. redease
automatically generates a unique key for you based on the HTTP method and URL (e.g., cache:GET:/users
).
TypeScript
// ✨ Behold, the magic of `redease` - Automatic Mode
import express from "express";
import { pool } from "../db";
import { cache, invalidate, createRedisClient } from "redease"; // Import the magic
const router = express.Router();
const redisClient = createRedisClient(); // Handles connection for you// Get all users - cached for 5 minutes. NO MANUAL KEY NEEDED!
router.get("/", cache({ ttl: 300, redisClient }), async (req, res) => {
console.log("📊 Fetching all users from database... (This only runs once every 5 minutes!)");
const { rows } = await pool.query("SELECT * FROM users ORDER BY id ASC");
res.json({
data: rows,
count: rows.length,
cached: false,
timestamp: new Date().toISOString(),
});
});
// The cache key is automatically: `cache:GET:/users`
// Get user by ID - cached for 10 minutes. STILL NO MANUAL KEY!
router.get("/:id", cache({ ttl: 600, redisClient }), async (req, res) => {
console.log(`📊 Fetching user ${req.params.id} from database...`);
const { rows } = await pool.query("SELECT * FROM users WHERE id = $1", [req.params.id]);
if (rows.length === 0) return res.status(404).json({ error: "User not found" });
res.json({ data: rows[0], cached: false, timestamp: new Date().toISOString() });
});
// The cache key is automatically: `cache:GET:/users/123`
// Create new user - INVALIDATES USING A PATTERN
router.post(
"/",
invalidate({ pattern: "cache:GET:/users*", redisClient }), // 🧹 Clears ALL /users and /users/:id cache
async (req, res) => {
const { name, email } = req.body;
const { rows } = await pool.query("INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *", [name, email]);
console.log("✅ Created user & invalidated all user caches");
res.status(201).json(rows[0]);
}
);
Style 2: Manual Key Control (For Maximum Power)
Sometimes you need more control, like including query parameters or a user ID in the key. redease
makes this easy and consistent.
TypeScript
// 🎛️ Manual Key Control - For when you need precision
router.get("/", cache({
ttl: 300,
redisClient,
key: (req) => `users:page:${req.query.page || 1}:limit:${req.query.limit || 50}` // Custom key logic
}), async (req, res) => {
// ... handler logic
});
// Cache key becomes: `users:page:2:limit:20`
router.post(
"/",
invalidate({
key: "users*", // You can still use a pattern for manual keys!
prefix: "myapp", // Optional custom prefix for your keys
redisClient
}),
async (req, res) => {
// ... handler logic
}
);
// This would invalidate all keys matching `myapp:users*`
Why this is infinitely better:
Zero Boilerplate: The
cache()
middleware wraps your handler and does everything for you.Flexible Key Generation: Start simple with automatic keys and upgrade to manual control only when you need it, all with the same clean API.
Powerful Invalidation: Use
pattern
to wipe out whole categories of cache (e.g.,cache:GET:/users*
) with one line. No more missing keys! This works for both automatic and manually named keys.Clean Separation: Your route logic is pure and focused on what it should do. Caching is a decorator, not a core feature.
Built-in Resilience: Connection errors? Timeouts?
redease
handles them gracefully so your app doesn't crash.
Key Features You’re Getting for Free
Smart Cache Keys: Choose between automatic or custom key generation. No collisions.
Pattern-Based Invalidation: The killer feature.
invalidate({ pattern: "*users*" })
is your best friend.Error Handling: If Redis is down, your app keeps working (it just bypasses the cache).
TypeScript Ready: Full type safety out of the box.
Customizable: Need a custom key? Pass a function. Need a different prefix? Set it globally.
Conclusion: You’d Be Silly Not To Use This
Let’s be real. You are a smart developer. Your time is valuable. Why spend it writing and, more importantly, debugging and maintaining the same caching boilerplate in every project?
redease
is not just another package; it's a force multiplier. It lets you ship faster, more reliable, and performant APIs with less code and fewer bugs. Whether you need the simplicity of automatic keys or the control of manual ones, it's got you covered.
Stop thinking about caching. Start implementing it.
Get Started Now:
npm install redease express ioredis
Check out the Package for installation guide and examples.
Check out the website.
Subscribe to my newsletter
Read articles from Priyangsu Banik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Priyangsu Banik
Priyangsu Banik
I assist B2B and eCommerce companies in building fast, user-friendly headless websites that enhance customer experience and streamline business operations, leveraging open-source technologies such as Medusa.js, Directus CMS, Strapi, Payload CMS, and Next.js. If you'd like to connect, explore collaboration opportunities, or just reach out, feel free to contact me at vectorlink.digital@gmail.com