Getting Started | @express-route-cache
Step-by-step guide to setting up production-grade route caching for Express.js in under a minute. Covers installation, quick start, Redis, SWR, and cache invalidation.
Getting Started
Installation
Install the core package along with any adapters you need:
# Core package (includes Memory adapter)
npm install @express-route-cache/core
# Redis adapter (recommended for production)
npm install @express-route-cache/redis ioredis
# Memcached adapter
npm install @express-route-cache/memcached memjsQuick Start
Setting up @express-route-cache takes less than a minute.
import express from "express";
import { createCache, createMemoryAdapter } from "@express-route-cache/core";
const app = express();
// 1. Initialize the Cache
const cache = createCache({
adapter: createMemoryAdapter(),
staleTime: 60, // Fresh for 60 seconds
gcTime: 300, // Kept stale for 5 more minutes
swr: true, // Enable Stale-While-Revalidate
});
// 2. Cache globally (GET requests only)
app.use(cache.middleware());
// 3. Or override per-route
app.get("/users/:id", cache.route({ staleTime: 120 }), (req, res) => {
res.json({ id: req.params.id, name: "John Doe" });
});
// 4. Invalidate instantly upon mutation
app.post("/users", cache.invalidate("/users"), (req, res) => {
// logic to create user...
res.status(201).json({ success: true });
});
// 5. Manual Data Caching (Standalone Fetch)
const data = await cache.fetch(
"users-list",
async () => {
return [{ id: 1, name: "John" }];
},
{ swr: true, retry: 3 },
);
app.listen(3000);Key Concepts
Invalidation
Invalidation is O(1). Instead of searching for keys to delete, we use "Epoch Versioning". Incrementing a version number makes all old cache entries instantly obsolete. See Epoch Invalidation for details.
🛠️ Advanced Patterns
1. Custom Cache Keys
Sometimes req.path isn't enough. You can override the key manually:
// Static key
cache.route({ key: "my-custom-key" });
// Dynamic key based on request (e.g., user-specific)
cache.route({ key: (req) => `user-${req.user.id}-profile` });2. Header-based Caching (vary)
If your API returns different data based on headers (like Authorization or Accept-Language), use the vary option:
cache.route({ vary: ["Authorization", "Accept-Language"] });3. Query Parameter Determinism
Avoid cache fragmentation by sorting query parameters:
// ?b=2&a=1 will be treated the same as ?a=1&b=2
cache.route({ sortQuery: true });4. Automatic Invalidation
You can tell the cache to automatically increment the version for a route pattern whenever a successful POST, PUT, PATCH, or DELETE request is made:
// Globally
const cache = createCache({ autoInvalidate: true, ... });
// Or per-route
app.post('/users', cache.route({ autoInvalidate: true }), createUser);5. Memory Protection (maxBodySize)
To prevent large responses (like 500MB videos) from crashing your Node process or filling up Redis, use maxBodySize. Any response larger than this will simply skip the cache.
cache.route({ maxBodySize: 1024 * 1024 * 5 }); // 5MB limit6. Standalone Data Caching
cache.fetch() lets you cache any async data — not just Express routes — with SWR, Stampede Protection, and retry support:
const users = await cache.fetch("all-users", () => db.users.findMany(), {
staleTime: 60,
swr: true,
retry: 2,
});See the Standalone Fetch guide for full details.
What's Next?
| Topic | Link |
|---|---|
| Full worked example with GET/POST/PATCH/DELETE | Example: Todo API |
| Real-world patterns (per-user, webhooks, warming) | Recipes |
| Production: Docker, K8s, health checks | Deployment |
| Testing your cached routes | Testing |
| Understanding fresh/stale windows visually | SWR Concepts |
| How O(1) invalidation works | Epoch Invalidation |
| Building a custom adapter | CacheClient Interface |