Zum Inhalt springen
Deutsch

Response-Cache-Middleware

cached umschließt einen Handler mit Response-Caching, key’d über eine vom Nutzer übergebene 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) => /* teures Lookup */)),
),
);

30 Sekunden pro Produkt-ID cachen. Der erste Request trifft den Handler; spätere GETs in den nächsten 30 s liefern die gecachte Response zurück.

interface ResponseCacheOptions {
cache: Cache;
ttlMs: number;
key: (req: HttpRequest) => string | Promise<string>;
keyPrefix?: string; // Default 'rsp:'
cacheStatuses?: ReadonlyArray<number>; // Default [200]
}
FeldZweck
cacheDas Cache-Backend. InMemoryCache für Single-Node; Redis für geteilt.
ttlMsWie lange jede gecachte Response gültig bleibt.
key(req)Leitet den Cache-Key aus dem Request her — typischerweise inklusive URL-Params, Mandanten-ID, Accept-Header usw. Erforderlich.
keyPrefixCache-Key-Namespace. Default 'rsp:', damit mehrere Response-Caches im selben Redis nicht kollidieren.
cacheStatusesWelche Statuscodes gecacht werden. Default [200] — nur 2xx-Responses landen im Cache.

Der Default-Key:

GET <pathname>?<sortierte-query-params> [Vary: accept=..., accept-encoding=...]

Sortierte Query-Parameter sorgen dafür, dass /items?a=1&b=2 und /items?b=2&a=1 denselben Cache-Eintrag treffen.

Mit key für per-User- / per-Mandant-Caches anpassen:

cached({
cache,
ttlMs: 30_000,
key: (req) => req.headers['x-tenant-id'] ?? 'public',
})(...)

Jetzt bekommen verschiedene Mandanten getrennte Cache-Einträge.

GET /api/products → cachen (stabil, leselastig)
POST /api/orders → nicht cachen (Mutation)
GET /api/users/me → standardmäßig nicht cachen (pro User)
GET /api/feed?cursor=X → cachen (ein Eintrag pro Cursor)

Die Middleware cached standardmäßig nur GETs. Mutationen (POST / PUT / DELETE / PATCH) gehen unverändert durch.

Die Middleware invalidiert nicht automatisch bei Writes. Caching verlässt sich auf TTL-Ablauf.

Für stärkere Konsistenz zwei Muster:

cached({ cache, ttlMs: 1_000 }); // 1 Sekunde

Sub-Sekunden-TTLs begrenzen die Veraltung. Gut für “Echtzeit-ähnliche” APIs.

// Nach einem Write, der /api/products betrifft:
await cache.delete('GET:/api/products');

Die Middleware exponiert den Cache; du kannst spezifische Keys mit delete invalidieren. Nützlich für Muster wie “Write nach /products invalidiert die Liste”.

import { RedisCache } from 'actor-ts';
cached({
cache: new RedisCache({ url: 'redis://...' }),
ttlMs: 30_000,
})(...)

Mit einem Redis-gestützten Cache teilen sich alle Pods denselben Cache — nützlich für Multi-Pod-Deployments, wenn du konsistentes Caching willst.

Bei Per-Pod-Caches (jeder Pod hat seinen eigenen InMemoryCache) liefern Pods kurz unterschiedliche gecachte Responses, bis die TTLs ausgerichtet sind. Oft akzeptabel; abhängig von den Konsistenzanforderungen.

cached({
cache,
ttlMs: 30_000,
varyHeaders: ['accept', 'accept-language', 'x-tenant-id'],
});

Jede einzigartige Kombination dieser Header bekommt einen eigenen Cache-Eintrag. Nützlich für:

  • Content-Negotiation — JSON-vs.-CBOR-Responses getrennt gecacht.
  • Lokalisierung — Responses pro Sprache.
  • Multi-Tenancy — Alternative zu key.