Docs/cachingv1.0.0-rc.1

Cache Middleware

cacheMiddleware adds HTTP response caching to any UDIOT middleware pipeline. It intercepts GET requests, returns cached responses on a hit, and stores successful responses on a miss. The headers.ts module provides utilities for building Cache-Control header strings, ETag generation, and conditional request handling.

Key Concepts

  • Request-level caching. The middleware operates on HttpContext objects inside a MiddlewarePipeline. It skips non-GET requests by default unless cacheMutations: true is passed.
  • Cache key derivation. By default, the key is built from method + path + sorted query parameters using CacheKeyGenerator.forRequest. A custom keyGenerator function can override this.
  • X-Cache header. The middleware adds X-Cache: HIT when serving from cache and X-Cache: MISS when the response is freshly computed.
  • Only 2xx responses are cached. Non-success responses (redirects, errors) are passed through without being stored.
  • Priority. The middleware registers with priority: -100, meaning it runs early in the pipeline before application middleware.

API Reference

cacheMiddleware(options?)

Returns a Middleware<HttpContext> object suitable for passing to pipeline.use().

function cacheMiddleware(options?: CacheMiddlewareOptions): Middleware<HttpContext>

CacheMiddlewareOptions:

Field Type Description
cacheManager CacheManager Override the global cache manager
ttl number TTL in seconds for stored responses
tags string[] Tags applied to each cached response
keyGenerator (context: HttpContext) => string Custom cache key function
condition (context: HttpContext) => boolean Return false to skip the cache for this request
cacheMutations boolean Cache non-GET requests when true (default false)

createCacheMiddleware(options?)

Returns the raw MiddlewareFunction<HttpContext> instead of the middleware object. Useful when you need to compose functions directly.

function createCacheMiddleware(options?: CacheMiddlewareOptions): MiddlewareFunction<HttpContext>

buildCacheControl(options)

Builds a Cache-Control header value from a structured options object.

function buildCacheControl(options: CacheControlOptions): string

CacheControlOptions:

Field Type Description
public boolean public directive
private boolean private directive
noCache boolean no-cache
noStore boolean no-store
maxAge number max-age=N
sMaxAge number s-maxage=N
mustRevalidate boolean must-revalidate
proxyRevalidate boolean proxy-revalidate
staleWhileRevalidate number stale-while-revalidate=N
staleIfError number stale-if-error=N
noTransform boolean no-transform
immutable boolean immutable

parseCacheControl(header)

Parses a Cache-Control header string back into CacheControlOptions.

function parseCacheControl(header: string): CacheControlOptions

CachePresets

Ready-made CacheControlOptions factories:

Preset Signature Description
noCache() () => CacheControlOptions no-cache, no-store, must-revalidate
private(maxAge) (maxAge: number) => CacheControlOptions Browser-only caching
public(maxAge, sMaxAge?) (maxAge, sMaxAge?) => CacheControlOptions CDN + browser caching
staleWhileRevalidate(maxAge, staleTime) (maxAge, staleTime) => CacheControlOptions Serve stale while revalidating
immutable(maxAge?) (maxAge?: number) => CacheControlOptions Static asset caching (default 1 year)

generateETag(content)

Generates a quoted ETag from a SHA-256 hash of content (first 32 hex characters).

function generateETag(content: string | Buffer): string
// returns e.g. '"a1b2c3d4e5f6..."'

shouldReturn304(requestHeaders, etag?, lastModified?)

Returns true when the request carries If-None-Match or If-Modified-Since headers that match the provided values.

function shouldReturn304(
  requestHeaders: Record<string, string>,
  etag?: string,
  lastModified?: Date
): boolean

Basic Usage

import { MiddlewarePipeline, cacheMiddleware } from '@tagna/udiot';
import type { HttpContext } from '@tagna/udiot';

const pipeline = new MiddlewarePipeline<HttpContext>();

// Cache all GET responses for 5 minutes
pipeline.use(cacheMiddleware({ ttl: 300 }));

// Add your application middleware and handlers
pipeline.use(authMiddleware());
pipeline.use(async (ctx, next) => {
  // ... handle the request
  await next();
});

Common Patterns

API response caching with tags

import { cacheMiddleware } from '@tagna/udiot';

const apiCache = cacheMiddleware({
  ttl: 300,
  tags: ['api-responses'],
  condition: (ctx) => !ctx.request.headers?.['x-no-cache'],
});

pipeline.use(apiCache);

Static asset headers

import { buildCacheControl, CachePresets } from '@tagna/udiot';

// In a route handler:
function staticAssetHandler(ctx: HttpContext) {
  const cacheControlValue = buildCacheControl(CachePresets.immutable());
  ctx.response.headers['Cache-Control'] = cacheControlValue;
  ctx.response.headers['ETag'] = generateETag(fileContent);
  ctx.response.status = 200;
  ctx.response.body = fileContent;
}

Conditional response (304 Not Modified)

import { generateETag, shouldReturn304 } from '@tagna/udiot';

async function handleRequest(ctx: HttpContext) {
  const content = await readFile();
  const etag = generateETag(content);

  if (shouldReturn304(ctx.request.headers ?? {}, etag)) {
    ctx.response.status = 304;
    return;
  }

  ctx.response.headers['ETag'] = etag;
  ctx.response.headers['Cache-Control'] = buildCacheControl(
    CachePresets.public(3600)
  );
  ctx.response.status = 200;
  ctx.response.body = content;
}

Stale-while-revalidate for API responses

import { buildCacheControl, CachePresets } from '@tagna/udiot';

// Serve fresh for 60 s, serve stale for up to 600 s while revalidating
const value = buildCacheControl(CachePresets.staleWhileRevalidate(60, 600));
// → 'public, max-age=60, stale-while-revalidate=600'

Integration Notes

  • cacheMiddleware stores the complete CachedResponse object (status, headers, body) and restores all three on a cache hit. Downstream handlers never run on a hit.
  • The middleware uses getGlobalCacheManager() unless an explicit cacheManager is provided. Configure the global manager before the pipeline executes its first request.
  • The priority: -100 value places this middleware early in the execution order relative to application middleware (which defaults to priority: 0).

Pitfalls

  • Only responses with HTTP status codes in the 200–299 range are stored. Redirect responses (3xx) and errors are passed through without caching.
  • If you pass cacheMutations: true, POST/PUT/DELETE responses will be cached. This is rarely the correct behavior; use it only for idempotent non-GET operations.
  • condition is evaluated after the request-method check. If you return false from condition, the request proceeds to the next middleware with no cache interaction in either direction.
  • buildCacheControl does not validate that mutually exclusive directives (e.g., public and private) are not set simultaneously. It emits whatever directives are truthy.
From the canonical UDIOT public documentation.Try it in the playground ↗