Node.js Redis Caching
A cache stores answers so your app can reuse them. Redis is a very fast in-memory database that serves as a popular cache. Caching makes slow pages load quickly and reduces the work on your main database.
The Notebook Idea
Suppose a friend asks you the distance to the airport. You check a map and write the answer on a sticky note. The next time someone asks, you read the note and skip the map. A cache works like that sticky note.
Without cache: Request --> App --> Database (slow) --> Response
With cache: Request --> App --> Redis
|
found? --> Response (fast)
missing? --> Database --> save in Redis --> Response
Why Redis Is Fast
Redis keeps data in memory instead of on a disk. Memory reads take microseconds. A typical database query takes milliseconds or more.
Key Terms
- Cache hit means Redis holds the answer.
- Cache miss means Redis lacks the answer.
- TTL means time to live. It sets how long Redis keeps a value.
- Invalidation means removing old data after a change.
Set Up Redis
Install Redis on your computer or run it in Docker.
docker run -d -p 6379:6379 redisInstall the Node.js client library.
npm install redisConnect to Redis
const { createClient } = require("redis");
const client = createClient({ url: "redis://localhost:6379" });
client.on("error", (err) => console.log("Redis error", err));
async function start() {
await client.connect();
console.log("Redis connected");
}
start();Basic Commands
await client.set("greeting", "Hello");
const value = await client.get("greeting"); // Hello
await client.set("otp:asha", "482910", { EX: 60 }); // expires in 60 seconds
await client.del("greeting");
const exists = await client.exists("greeting"); // 0The option EX: 60 sets a 60-second lifetime. Redis deletes the key by itself afterward.
Store Objects
Redis stores text. Convert objects to JSON text before saving.
const user = { id: 7, name: "Ravi" };
await client.set("user:7", JSON.stringify(user), { EX: 300 });
const saved = JSON.parse(await client.get("user:7"));Cache-Aside Pattern
This pattern checks Redis first and falls back to the database.
async function getProduct(id) {
const key = "product:" + id;
const cached = await client.get(key);
if (cached) {
return JSON.parse(cached); // cache hit
}
const product = await Product.findById(id); // cache miss
await client.set(key, JSON.stringify(product), { EX: 600 });
return product;
}Cache Middleware for Express
function cacheRoute(seconds) {
return async (req, res, next) => {
const key = "route:" + req.originalUrl;
const hit = await client.get(key);
if (hit) {
return res.json(JSON.parse(hit));
}
const originalJson = res.json.bind(res);
res.json = (body) => {
client.set(key, JSON.stringify(body), { EX: seconds });
return originalJson(body);
};
next();
};
}
app.get("/api/products", cacheRoute(60), async (req, res) => {
const items = await Product.find();
res.json(items);
});The first visitor triggers the database query. Visitors in the next 60 seconds receive the saved copy.
Remove Old Data
Cached data becomes wrong after an update. Delete the key when the source changes.
app.put("/api/products/:id", async (req, res) => {
await Product.findByIdAndUpdate(req.params.id, req.body);
await client.del("product:" + req.params.id);
await client.del("route:/api/products");
res.send("Updated");
});Other Uses for Redis
- Store login sessions.
- Count requests for rate limiting.
- Hold queues of background jobs.
- Share messages between servers with publish and subscribe.
Good Habits
- Set a TTL on every cached value.
- Use clear key names such as user:7 and product:12.
- Cache data that many people read and few people change.
- Skip caching for private, per-user data unless the key includes the user id.
- Let the app continue to work when Redis goes down.
Practice Tasks
- Cache a slow function result for 30 seconds and measure the speed gain.
- Print "hit" or "miss" in the log for each request.
- Delete the cached value after you update the source data.
