How to implement caching in Express.js for better performance?
Caching in Express.js
Caching stores frequently requested data in a fast-access layer to reduce response time and server load. It's one of the most impactful performance optimizations.
Types of Caching
1. In-Memory Caching (node-cache)
Best for single-server deployments:
js
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300, checkperiod: 60 });
function cacheMiddleware(duration) {
return (req, res, next) => {
const key = req.originalUrl;
const cached = cache.get(key);
if (cached) {
return res.json(cached);
}
const originalJson = res.json.bind(res);
res.json = (body) => {
cache.set(key, body, duration);
originalJson(body);
};
next();
};
}
// Cache for 5 minutes
app.get('/api/products', cacheMiddleware(300), async (req, res) => {
const products = await db.query('SELECT * FROM products');
res.json(products);
});2. Redis Caching (Distributed)
Best for multi-server deployments:
js
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
function redisCacheMiddleware(ttl = 300) {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
} catch (err) {
console.error('Redis error:', err);
}
const originalJson = res.json.bind(res);
res.json = async (body) => {
try {
await redis.setex(key, ttl, JSON.stringify(body));
} catch (err) {
console.error('Redis set error:', err);
}
originalJson(body);
};
next();
};
}
app.get('/api/products', redisCacheMiddleware(600), getProducts);3. HTTP Cache Headers
Let browsers and CDNs cache responses:
js
// Cache-Control for public cacheable content
app.get('/api/products', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // 5 min
res.json(products);
});
// No cache for private data
app.get('/api/profile', auth, (req, res) => {
res.set('Cache-Control', 'private, no-cache');
res.json(user);
});
// ETag-based caching
const etag = require('etag');
app.get('/api/data', (req, res) => {
const data = getData();
const tag = etag(JSON.stringify(data));
if (req.headers['if-none-match'] === tag) {
return res.status(304).end(); // Not Modified
}
res.set('ETag', tag);
res.json(data);
});4. Cache Invalidation
js
// Invalidate on write operations
app.post('/api/products', async (req, res) => {
const product = await createProduct(req.body);
// Invalidate related caches
cache.del('/api/products');
await redis.del('cache:/api/products');
res.status(201).json(product);
});
// Pattern-based invalidation with Redis
async function invalidatePattern(pattern) {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
// Invalidate all product caches
await invalidatePattern('cache:/api/products*');Caching Strategy Comparison
| Strategy | Speed | Scalability | Persistence | Use Case |
|---|---|---|---|---|
| In-memory | Fastest | Single server | No | Small apps, dev |
| Redis | Very fast | Multi-server | Yes | Production APIs |
| HTTP headers | Client-side | CDN-friendly | Browser | Static content |
| CDN | Fastest for users | Global | Edge | Assets, public APIs |
What to Cache
| Cache | Don't Cache |
|---|---|
| Database query results | User-specific data (unless Redis) |
| External API responses | Real-time data (stock prices) |
| Computed/aggregated data | Authentication tokens |
| Static content | Write-heavy endpoints |
Golden rule: Cache aggressively, invalidate carefully. Start with short TTLs (30-60s) and increase based on data change frequency. Always have a way to force-invalidate when data changes.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.