Implementing a Redis-Powered Cache Middleware in Strapi
Caching is an essential optimization technique in web development, helping reduce database load and significantly speeding up response times. If you’re building a Strapi-based application, adding a caching layer can make your API lightning-fast while maintaining scalability.
Requirements
{
"dependencies": {
"@strapi/strapi": "5.1.0",
"ioredis": "^5.4.1"
}
}
🚀 Why Add Caching in Strapi?
Strapi provides a robust backend for APIs, but database queries can become a bottleneck as traffic grows. By adding caching to Strapi:
- Boost API response time: Serve cached data directly, avoiding repetitive database queries.
- Reduce backend load: Offload frequent reads to an in-memory store like Redis.
- Enhance scalability: Handle higher traffic without straining your database.
Flow
Normal flow
clear_cache Flow
📦 Installing Redis for Caching
Install ioredis to interact with Redis, because we're using redis as cache provider.
npm install ioredis
🛠️ Setting Up Middleware in Strapi
To add a custom middleware, Strapi requires you to:
- Register it in the middleware configuration file (
./config/middlewares.ts). - Create the middleware file in directory
./src/middlewares/, then implement the middleware logic.
1. Middleware Registration
In ./config/middlewares.ts, register your middleware as a global middleware:
register new global::cache-middleware
export default [
'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
'global::cache-middleware' // Register cache middleware here
];
2. 🧩 Create the Cache Middleware file
Create a new middleware file, cache-middleware.ts, in the ./src/middlewares/ directory
Full Implementation
import Redis from 'ioredis';
export default (config, { strapi }) => {
const CACHE_TIMEOUT = 1000; // Connection timeout
const CACHE_EXPIRATION = process.env.REDIS_CACHE_EXPIRATION || 3600; // Default TTL: 1 hour
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
connectTimeout: CACHE_TIMEOUT,
commandTimeout: CACHE_TIMEOUT,
retryStrategy: (times) => (times <= 1 ? CACHE_TIMEOUT : null)
});
redis.on('error', (error) => {
strapi.log.error('Redis connection error:', error);
});
return async (ctx, next) => {
const start = Date.now();
const matches = ctx.path.match(/^\/?api(\/.+)$/);
// Skip non-GET requests or non-API paths
if (ctx.method !== 'GET' || !matches || matches.length !== 2) {
await next();
return;
}
const queryParams = new URLSearchParams(ctx.querystring);
const shouldClearCache = queryParams.get('clear_cache') === '1';
queryParams.delete('clear_cache'); // Remove clear_cache from cache key
const cacheKey = `strapi:cache:${ctx.path}?${decodeURIComponent(queryParams.toString())}`;
const tryRedisOperation = async (operation, retryCount = 1) => {
try {
return await operation();
} catch (error) {
if (retryCount > 0) {
strapi.log.warn('Redis operation failed, retrying...');
return tryRedisOperation(operation, retryCount - 1);
}
throw error;
}
};
try {
if (shouldClearCache) {
await tryRedisOperation(() => redis.del(cacheKey));
strapi.log.info(`Cache cleared for: ${cacheKey}`);
ctx.set('X-Cache', 'CLEARED');
}
const cachedResponse = await tryRedisOperation(() => redis.get(cacheKey));
if (cachedResponse && !shouldClearCache) {
const { body, headers, status } = JSON.parse(cachedResponse);
ctx.body = body;
ctx.status = status;
Object.entries(headers).forEach(([key, value]) => ctx.set(key, value as string));
ctx.set('X-Cache', 'HIT');
} else {
await next();
if (ctx.status === 200) {
const response = {
body: ctx.body,
headers: ctx.response.headers,
status: ctx.status
};
await tryRedisOperation(() =>
redis.setex(cacheKey, CACHE_EXPIRATION, JSON.stringify(response))
);
ctx.set('X-Cache', shouldClearCache ? 'CLEARED+MISS' : 'MISS');
} else {
ctx.set('X-Cache', 'SKIP');
}
}
} catch (error) {
ctx.set('X-Cache', 'ERROR');
strapi.log.error('Cache middleware error:', error);
await next();
}
ctx.set('X-Response-Time', `${Date.now() - start}ms`);
};
};
Environment variables to configure cache
Make sure to run command below, before start the strapi app
export REDIS_CACHE_EXPIRATION=3600
export REDIS_URL=redis://:password@localhost:6379
export CACHE_TIMEOUT=1000
🔄 Cache Invalidation with Query Parameters
Add ?clear_cache=1 to any API endpoint to clear its cache. For example:
GET /api/users?clear_cache=1
📝 Cache Response Headers
To track cache usage, the middleware sets an X-Cache header:
HIT: Data was served from the cache.MISS: Data was retrieved from the source and cached.CLEARED: Cache was invalidated for the request.CLEARED+MISS: Cache was cleared, and fresh data was stored.ERROR: Redis operation failed.
🚀 Conclusion
Adding a Redis-powered cache middleware to Strapi can drastically improve performance, scalability, and user experience. By following this guide, you’ll have a flexible caching solution ready to handle high traffic while keeping your application responsive.
Give your Strapi API a performance boost—start caching with Redis today! 🚀
