Skip to content

Cache Middleware

Response caching middleware for Strata services. Caches successful operation responses and serves them on subsequent requests with matching payloads, skipping the operation handler entirely.

Supports three built-in cache stores (memory, Redis, null) and custom stores via the ICacheStore interface.

Installation

bash
npm install @strata-js/middleware-cache

Peer dependency: @strata-js/strata >= 1.4.0 || ^2.0.0

Usage

Create a CacheMiddleware instance with a configuration object, then register it on an operation (or context/service):

typescript
import { StrataContext } from '@strata-js/strata';
import { CacheMiddleware } from '@strata-js/middleware-cache';

const ctx = new StrataContext('products');

const cache = new CacheMiddleware({ kind: 'memory', key: 'products' });

ctx.registerOperation('getProduct', async (request) =>
{
    return { product: await db.findProduct(request.payload.productId) };
}, [], [ cache ]);

On the first request, the handler runs and the response is stored. On subsequent requests with an identical payload, the cached response is returned immediately without calling the handler.

Configuration

Pass a configuration object to the CacheMiddleware constructor. The kind field determines which store is used.

Memory Cache

Uses node-cache for in-process caching. Good for development and single-instance services.

typescript
const cache = new CacheMiddleware({
    kind: 'memory',
    key: 'my-service',
    options: {
        stdTTL: 3600,       // TTL in seconds (default: 86400 = 24 hours)
        checkperiod: 120,   // Cleanup interval in seconds
    },
});
OptionTypeDefaultDescription
kind'memory'--Selects the in-memory store.
keystring'cache'Prefix for cache keys.
optionsNodeCacheOptions{ stdTTL: 86400 }Passed directly to node-cache. See node-cache options.

Redis Cache

Uses ioredis for distributed caching across multiple service instances.

typescript
const cache = new CacheMiddleware({
    kind: 'redis',
    key: 'my-service',
    options: {
        host: 'localhost',
        port: 6379,
        db: 1,
    },
    ttl: 60 * 60 * 24,  // TTL in seconds
});
OptionTypeDefaultDescription
kind'redis'--Selects the Redis store.
keystring'cache'Prefix for cache keys.
optionsRedisOptions--Passed directly to ioredis. See ioredis options.
ttlnumber--Time-to-live in seconds. If omitted, entries never expire.

The Redis store includes automatic reconnection with exponential backoff and keepalive (3-minute idle delay).

Null Cache (Disabled)

Disables caching entirely. Useful for turning off caching via configuration without changing code.

typescript
const cache = new CacheMiddleware({ kind: 'none' });
OptionTypeDescription
kind'none'Selects the null store (all gets return null, all sets are no-ops).

Custom Cache Stores

Implement the ICacheStore interface to use any backing store:

typescript
import { CacheMiddleware } from '@strata-js/middleware-cache';
import type { ICacheStore } from '@strata-js/middleware-cache';

class DynamoStore implements ICacheStore
{
    async get(key : string) : Promise<unknown>
    {
        const item = await dynamo.getItem({ Key: { pk: key } });
        return item?.value ?? null;
    }

    async set(key : string, payload : unknown) : Promise<unknown>
    {
        await dynamo.putItem({ Key: { pk: key }, value: payload });
        return key;
    }

    // Optional -- called during service shutdown
    async teardown() : Promise<void>
    {
        await dynamo.close();
    }
}

const cache = new CacheMiddleware(new DynamoStore(), { kind: 'custom', key: 'my-service' });

ICacheStore Interface

typescript
interface ICacheStore
{
    get(key : string) : Promise<unknown>;
    set(key : string, payload : unknown) : Promise<unknown>;
    teardown ?: () => Promise<void>;
}
MethodDescription
get(key)Retrieve a cached value by key. Return null or undefined for a cache miss.
set(key, payload)Store a value under the given key.
teardown()Optional. Called when the service shuts down -- close connections, flush buffers, etc.

When using a custom store, pass the store instance as the first argument and a { kind: 'custom' } configuration as the second.

How Caching Works

Cache Key Generation

Cache keys are built from four components, joined by ::

<key>:<context>:<operation>:<sorted-payload-json>

For example, a request to products/getProduct with payload { productId: '42' } and key 'my-service' produces:

my-service:products:getProduct:{"productId":"42"}

Payload keys are sorted alphabetically before serialization, so { b: 2, a: 1 } and { a: 1, b: 2 } produce identical cache keys.

Request Lifecycle

  1. beforeRequest -- Checks the cache for a matching key. On a hit, calls request.succeed() with the cached value (short-circuiting the handler). Sets request.cacheHit to true or false.
  2. success -- If the request was not a cache hit, stores the response in the cache.
  3. failure -- No-op. Failed responses are never cached.
  4. teardown -- Calls store.teardown() if the store implements it (e.g., disconnects Redis).

Examples

Operation-Level Caching

The most common pattern -- cache responses for a single read operation:

typescript
import { StrataContext } from '@strata-js/strata';
import { CacheMiddleware } from '@strata-js/middleware-cache';

const products = new StrataContext('products');

const cache = new CacheMiddleware({
    kind: 'redis',
    key: 'product-service',
    options: { host: 'redis.internal', port: 6379 },
    ttl: 300,  // 5 minutes
});

products.registerOperation('getProduct', async (request) =>
{
    return { product: await db.findProduct(request.payload.productId) };
}, [], [ cache ]);

Switching Stores by Environment

Use the kind field to swap stores without changing any other code:

typescript
import { CacheMiddleware } from '@strata-js/middleware-cache';
import type { CacheMiddlewareConfiguration } from '@strata-js/middleware-cache';

function buildCacheConfig() : CacheMiddlewareConfiguration
{
    if(process.env.NODE_ENV === 'production')
    {
        return {
            kind: 'redis',
            key: 'my-service',
            options: { host: process.env.REDIS_HOST!, port: 6379 },
            ttl: 3600,
        };
    }

    if(process.env.DISABLE_CACHE === 'true')
    {
        return { kind: 'none' };
    }

    return { kind: 'memory', key: 'my-service' };
}

const cache = new CacheMiddleware(buildCacheConfig());

Context-Level Caching

Cache every operation in a context:

typescript
const lookups = new StrataContext('lookups');
const cache = new CacheMiddleware({ kind: 'memory', key: 'lookups' });

lookups.useMiddleware(cache);

lookups.registerOperation('getStates', async () => fetchStates());
lookups.registerOperation('getCountries', async () => fetchCountries());