@hoajs/cache
Provides simple and reliable response caching for Hoa. Built on the Web-standard caches API and works in Cloudflare Workers, Deno, Bun, Node.js, and other runtimes that support it.
If the runtime doesn't support globalThis.caches, the middleware becomes a no-op and won't affect request handling.
Quick Start
js
import { Hoa } from 'hoa'
import { cache } from '@hoajs/cache'
const app = new Hoa()
app.use(cache())
app.use(async (ctx) => {
ctx.res.body = 'Hello, Hoa!'
})
export default appOptions
ts
interface CacheOptions {
cacheName?: string | ((ctx: HoaContext) => Promise<string> | string)
wait?: boolean
cacheControl?: string
vary?: string | string[]
keyGenerator?: (ctx: HoaContext) => Promise<string> | string
cacheableStatusCodes?: number[]
}cacheName(default'cache')- Cache store name; supports string, function, or async function.
- Useful for maintaining separate caches per route or context.
wait(defaultfalse)- Whether to wait for
cache.putto resolve before continuing. - In Deno or environments with an execution context, prefer
trueor providectx.executionCtxand usewaitUntil.
- Whether to wait for
cacheControl- Directive string for the
Cache-Controlheader; merged and de-duplicated when the response already has this header.
- Directive string for the
vary- Sets the
Varyheader; merges with existing values, de-duplicates case-insensitively, and normalizes to lowercase. - If
*is present, an error is thrown (forbids wildcard that prevents effective caching).
- Sets the
keyGenerator- Generates a cache key for each request; defaults to the request URL (
ctx.req.href). - May use route params, query params, or context; supports async.
- Generates a cache key for each request; defaults to the request URL (
cacheableStatusCodes(default[200])- Array of status codes that can be cached.
Examples
Basic caching (200 cached by default)
js
app.use(cache())Set Cache-Control and Vary
js
app.use(cache({
cacheControl: 'public, max-age=60',
vary: ['Accept', 'Accept-Language']
}))
app.use(async (ctx) => {
// If your handler sets headers, the middleware will merge and de-duplicate
ctx.res.set('Cache-Control', 'no-cache')
ctx.res.set('Vary', 'Accept-Encoding')
ctx.res.body = 'data'
})Result:
Cache-Control:no-cache, public, max-age=60(deduplicated by directive name and appends missing values).Vary:accept, accept-encoding, accept-language(case-insensitive dedupe, normalized to lowercase).
Dynamic/async cacheName (per route/tenant isolation)
js
app.use(cache({
cacheName: async (ctx) => `tenant:${ctx.req.headers.get('x-tenant') ?? 'default'}`
}))Custom cache key (based on params/language)
js
app.use(cache({
keyGenerator: (ctx) => {
const lang = ctx.req.headers.get('accept-language')?.split(',')[0] ?? 'en'
return `${ctx.req.href}|lang:${lang}`
}
}))Control write timing (wait / executionCtx)
js
app.use(cache({ wait: true })) // explicitly wait for the write to finish
// If your runtime provides an execution context (e.g. Cloudflare Workers),
// ensure it's available on ctx.executionCtx.
// ctx.executionCtx?.waitUntil(...) schedules the write in the background.Cache only specific status codes
js
app.use(cache({ cacheableStatusCodes: [200, 204] }))