Implementing a Redis-Powered Cache Middleware in Strapi

Senior Principal Engineer
··4 min read
Implementing a Redis-Powered Cache Middleware in Strapi

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

Mermaid diagram

clear_cache Flow

Mermaid diagram

📦 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:

  1. Register it in the middleware configuration file (./config/middlewares.ts).
  2. 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! 🚀