Response-Cache-Middleware
cached umhüllt einen Handler mit Response-Caching, verschlüsselt
über eine selbst gelieferte key(req)-Funktion. Ein Cache-Treffer
liefert die gespeicherte Response, ohne den darunterliegenden Handler
aufzurufen.
import { cached, path, get } from 'actor-ts/http';import { InMemoryCache } from 'actor-ts';
const productCache = cached({ cache: new InMemoryCache(), ttlMs: 30_000, key: (req) => `products:${pathParam(req, 'id')}`,});
const routes = path('api', path('products', get(productCache(async (req) => /* expensive lookup */)), ),);30 Sekunden Cache pro Produkt-ID. Der erste Request trifft den Handler; nachfolgende GETs in den nächsten 30 s liefern die gecachte Response.
Konfiguration
Abschnitt betitelt „Konfiguration“type ResponseCacheOptions = { cache: Cache; ttlMs: number; key: (req: HttpRequest) => string | Promise<string>; keyPrefix?: string; // default 'rsp:' cacheStatuses?: ReadonlyArray<number>; // default [200]};| Feld | Zweck |
|---|---|
cache | Das Cache-Backend. InMemoryCache für Single-Node; Redis für geteilt. |
ttlMs | Wie lange jede gecachte Response gültig bleibt. |
key(req) | Leitet den Cache-Key aus dem Request ab — enthält typischerweise URL-Params, Tenant-ID, Accept-Header usw. Pflicht. |
keyPrefix | Cache-Key-Namespace. Default 'rsp:', damit sich mehrere Response-Caches im selben Redis nicht überschneiden. |
cacheStatuses | Welche Status-Codes cachebar sind. Default [200] — nur 2xx-Responses werden gecacht. |
Cache-Key-Zusammensetzung
Abschnitt betitelt „Cache-Key-Zusammensetzung“Die key(req)-Funktion ist der gesamte Cache-Key-Vertrag — was du
dort aufnimmst, bestimmt, was separat gecacht wird. Gängige Muster:
// Per-URL-path key:key: (req) => req.path,
// Per-tenant:key: (req) => `${req.headers['x-tenant-id'] ?? 'public'}:${req.path}`,
// Per-locale + content type:key: (req) => `${req.headers['accept-language']}:${req.headers['accept']}:${req.path}`,Baue den Key so, dass er über äquivalente Requests deterministisch, aber über getrennt zu cachende Responses eindeutig ist.
Wann cachen
Abschnitt betitelt „Wann cachen“GET /api/products → cache (stable, read-heavy)POST /api/orders → don't wrap with cached() (mutation)GET /api/users/me → cache only with per-user keyGET /api/feed?cursor=X → cache (one entry per cursor)Der Wrapper ist eine Handler-Ebenen-Entscheidung — du wählst, welche
Handler du umhüllst. Mutationen sollten typischerweise
idempotent verwenden.
Cache-Invalidierung
Abschnitt betitelt „Cache-Invalidierung“cached invalidiert nicht automatisch bei Writes — die
Veraltung ist durch ttlMs begrenzt.
Für stärkere Konsistenz zwei Muster:
Kurze TTLs
Abschnitt betitelt „Kurze TTLs“cached({ cache, ttlMs: 1_000, key }); // 1 secondSub-Sekunden-TTLs begrenzen die Veraltung. Gut für „echtzeit-nahe” APIs.
Manuelle Invalidierung
Abschnitt betitelt „Manuelle Invalidierung“// After a write that affects products:42, invalidate the cache:await cache.delete('rsp:products:42'); // 'rsp:' is the default keyPrefixDer Cache ist einfach ein Cache — du kannst den mit Prefix
versehenen Key direkt deleten, wenn ein Write ihn invalidieren soll.
Geteilter Cache über Pods
Abschnitt betitelt „Geteilter Cache über Pods“import { RedisCache, RedisCacheOptions } from 'actor-ts';
const redisCacheOptions = RedisCacheOptions.create().withUrl('redis://...');const productCache = cached({ cache: new RedisCache(redisCacheOptions), ttlMs: 30_000, key: (req) => `products:${pathParam(req, 'id')}`,});Mit einem Redis-gestützten Cache teilen sich alle Pods denselben Cache — nützlich für Multi-Pod-Deployments, in denen du konsistentes Caching willst.
Für Per-Pod-Caches (jeder Pod hat seinen eigenen InMemoryCache) können Pods kurzzeitig unterschiedliche gecachte Responses liefern, bis sich die TTLs angleichen. Oft akzeptabel; hängt von den Konsistenzanforderungen ab.
Verschiedene Status-Codes cachen
Abschnitt betitelt „Verschiedene Status-Codes cachen“cached({ cache, ttlMs: 30_000, key, cacheStatuses: [200, 404], // cache both hits AND "not found"s});Cache 404s, wenn du viele Lookups für nicht existierende IDs hast —
spart wiederholte DB-Zugriffe. Default ist nur [200], damit ein
transientes 500 nicht 30 Sekunden lang hängen bleibt.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- HTTP-Übersicht — das große Bild.
- Rate-Limit-Middleware — komplementäre Middleware.
- Idempotency-Key-Middleware — für Write-Dedup.
- Cache-Übersicht — das Backend, das diese Middleware nutzt.
