How I Eliminated 90% of My Express Caching Code

Priyangsu BanikPriyangsu Banik
6 min read

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:

  1. Connect to Redis.

  2. Before a request, check if the data is cached.

  3. If it is, parse it and return it.

  4. If it’s not, run the expensive DB query.

  5. After the query, stringify the result and stuff it into Redis with a SETEX.

  6. Pray you remembered to handle all the error cases.

  7. When data changes, try to remember all the possible cache keys to delete.

  8. Fail at step 7, causing users to see stale data.

  9. 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:

  1. cache(): Decorate a GET route to automatically cache its response.

  2. 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

  1. Smart Cache Keys: Choose between automatic or custom key generation. No collisions.

  2. Pattern-Based Invalidation: The killer feature. invalidate({ pattern: "*users*" }) is your best friend.

  3. Error Handling: If Redis is down, your app keeps working (it just bypasses the cache).

  4. TypeScript Ready: Full type safety out of the box.

  5. 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.

1
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