Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [
text: '3rd-party Middleware',
link: '/docs/middleware/third-party',
},
{
text: 'Universal Cache (3rd-party)',
link: '/docs/middleware/third-party/universal-cache',
},
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions docs/middleware/third-party.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,6 @@ Most of this middleware leverages external libraries.
- [RONIN (Database)](https://github.com/ronin-co/hono-client)
- [Session](https://github.com/honojs/middleware/tree/main/packages/session)
- [tsyringe](https://github.com/honojs/middleware/tree/main/packages/tsyringe)
- [Universal Cache](https://github.com/honojs/middleware/tree/main/packages/universal-cache)
- [Universal Cache Guide](/docs/middleware/third-party/universal-cache)
- [User Agent based Blocker](https://github.com/honojs/middleware/tree/main/packages/ua-blocker)
223 changes: 223 additions & 0 deletions docs/middleware/third-party/universal-cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Universal Cache Middleware

`@hono/universal-cache` is a third-party middleware package for response and function-level caching in Hono apps.

It supports:

- response caching with `cacheMiddleware()`
- request-scoped defaults with `cacheDefaults()`
- function result caching with `cacheFunction()`
- stale-while-revalidate (SWR)
- custom keying, serialization, and cache invalidation hooks

## Install

```txt
npm i @hono/universal-cache
```

## Import

```ts
import { Hono } from 'hono'
import {
cacheDefaults,
cacheFunction,
cacheMiddleware,
} from '@hono/universal-cache'
```

## Default Behavior

Universal Cache uses an in-memory `unstorage` driver by default, with these defaults:

```ts
const baseDefaults = {
base: 'cache',
maxAge: 1,
staleMaxAge: 0,
swr: true,
keepPreviousOn5xx: true,
revalidateHeader: 'x-cache-revalidate',
}

const middlewareDefaults = {
...baseDefaults,
group: 'hono/handlers',
methods: ['GET', 'HEAD'],
varies: [],
}

const functionDefaults = {
...baseDefaults,
group: 'hono/functions',
}
```

Default key strategy:

- `cacheMiddleware()`: hash of `pathname + search`, plus hashed `varies` header values
- `cacheFunction()`: hash of serialized function arguments
- middleware name default: normalized request path (e.g. `/api/items` -> `api:items`)
- function name default: `fn.name` (fallback: `_`)
- integrity default: auto-derived hash (middleware by cache namespace, function by function source)

## Usage

### Cache route responses

```ts
const app = new Hono()

app.get(
'/api/items',
cacheMiddleware({
maxAge: 60,
staleMaxAge: 30,
swr: true,
}),
(c) => c.json({ items: ['a', 'b'] })
)
```

### Set global cache defaults

```ts
app.use(
'*',
cacheDefaults({
maxAge: 60,
staleMaxAge: 30,
swr: true,
})
)
```

### Use storage adapters (via unstorage)

```ts
import { createStorage } from 'unstorage'
import redisDriver from 'unstorage/drivers/redis'

app.use(
'*',
cacheDefaults({
storage: createStorage({
driver: redisDriver({ url: 'redis://localhost:6379' }),
}),
maxAge: 60,
staleMaxAge: 30,
swr: true,
})
)
```

### Override global defaults per route

```ts
app.get(
'/api/slow/items',
cacheMiddleware({
config: { maxAge: 300, staleMaxAge: 120 },
varies: ['accept-language'],
}),
(c) => c.json({ ok: true })
)
```

### Cache function results

```ts
const getStats = cacheFunction(
async (id: string) => {
return { id, ts: Date.now() }
},
{
maxAge: 60,
getKey: (id) => id,
}
)
```

## Revalidation and Invalidation

By default, route cache can be manually revalidated by sending:

```txt
x-cache-revalidate: 1
```

You can also use hooks such as:

- `shouldBypassCache`
- `shouldInvalidateCache`

for custom logic.

## Options Summary

Base options (`cacheMiddleware` + `cacheFunction`):

- `storage`: custom unstorage instance
- `base`: storage prefix (default: `cache`)
- `group`: storage group segment (default middleware: `hono/handlers`, function: `hono/functions`)
- `name`: cache entry name (default inferred from route/function name)
- `hash`: custom hash function for default keys/integrity
- `integrity`: manual integrity value for cache busting
- `maxAge`: max age in seconds (default: `1`)
- `staleMaxAge`: stale max age in seconds, `-1` means unlimited stale (default: `0`)
- `swr`: enable stale-while-revalidate (default: `true`)
- `keepPreviousOn5xx`: keep previous entry when refresh fails in invalidation paths (default: `true`)
- `revalidateHeader`: revalidation header name (default: `x-cache-revalidate`)

Middleware-only:

- `config`: request-scoped defaults to apply before route options
- `methods`: cacheable methods (default: `GET`, `HEAD`)
- `varies`: request headers to include in key
- `getKey`: custom request key builder
- `serialize` / `deserialize`: custom response cache format
- `validate`: validate cached response entries
- `shouldBypassCache`: skip cache for matching requests
- `shouldInvalidateCache`: invalidate entry before refresh

Function-only:

- `getKey`: custom key builder from function args
- `serialize` / `deserialize`: custom function entry format
- `validate`: validate cached function entries
- `shouldBypassCache`: skip cache for matching calls
- `shouldInvalidateCache`: invalidate entry before refresh

## Key APIs

### `cacheMiddleware(options | maxAge)`

Caches route responses (default methods: `GET`, `HEAD`).

### `cacheDefaults(options)`

Sets request-scoped default options for cache middleware.

### `cacheFunction(fn, options | maxAge)`

Wraps a function with caching behavior.

### Storage/default helpers

- `createCacheStorage()`
- `setCacheStorage()` / `getCacheStorage()`
- `setCacheDefaults()` / `getCacheDefaults()`

## Notes

- Cached route responses drop `set-cookie` and hop-by-hop headers.
- Responses with `cache-control: no-store` or `no-cache` are not cached.
- Cached function values should be serializable for your selected storage driver.

## Links

- Source package:
`https://github.com/honojs/middleware/tree/main/packages/universal-cache`
- Third-party middleware index:
`/docs/middleware/third-party`