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
HttpContextobjects inside aMiddlewarePipeline. It skips non-GET requests by default unlesscacheMutations: trueis passed. - Cache key derivation. By default, the key is built from
method + path + sorted query parametersusingCacheKeyGenerator.forRequest. A customkeyGeneratorfunction can override this. X-Cacheheader. The middleware addsX-Cache: HITwhen serving from cache andX-Cache: MISSwhen the response is freshly computed.- Only
2xxresponses 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
cacheMiddlewarestores the completeCachedResponseobject (status, headers, body) and restores all three on a cache hit. Downstream handlers never run on a hit.- The middleware uses
getGlobalCacheManager()unless an explicitcacheManageris provided. Configure the global manager before the pipeline executes its first request. - The
priority: -100value places this middleware early in the execution order relative to application middleware (which defaults topriority: 0).
Pitfalls
- Only responses with HTTP status codes in the
200–299range 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. conditionis evaluated after the request-method check. If you returnfalsefromcondition, the request proceeds to the next middleware with no cache interaction in either direction.buildCacheControldoes not validate that mutually exclusive directives (e.g.,publicandprivate) are not set simultaneously. It emits whatever directives are truthy.