Cache
Avoid repeating expensive reads and computation with a small JSON cache on Cloudflare.
Available in Goribu 0.9.0. This guide describes request-scoped JSON caching through req.cache.
Read and cache
req.cache is available in handlers and middleware without a binding or configuration. Read a value, load it from its source on a miss, then cache the result. The D1 query in this example assumes a configured database and an existing polls table; see D1 for setup.
export async function GET(req, res) {
const key = "/polls/recent:v1";
let polls = await req.cache.get(key);
if (polls === null) {
polls = await req.d1`SELECT id, title FROM polls ORDER BY id DESC LIMIT 20`.all();
await req.cache.set(key, polls, { ttl: 300 });
}
return res.json({ polls });
}Check misses with === null. Cached false, 0, and empty strings are valid results. Storing null is allowed but cannot be distinguished from a miss; use an object such as { found: false } to cache an absent result.
Operations
| Call | Behavior |
|---|---|
get(key) | Returns parsed JSON, or null on a miss or cache failure. |
set(key, value, options?) | Attempts to store JSON. The only option is ttl, in seconds, defaulting to 60. |
delete(...keys) | Attempts to remove one or more entries in the current data center. |
Await each operation. set and delete resolve to undefined. TTL must be a positive safe integer: zero, fractions, negative values, and non-finite numbers are rejected. There is no permanent setting. Entries can be evicted before their TTL expires, and a resolved write does not guarantee that Cloudflare retained it.
Values use ordinary JSON serialization. Dates become strings, undefined object properties are omitted, and non-finite numbers become null. Circular values, BigInts, and top-level values that produce no JSON, such as undefined, functions, or symbols, are rejected.
type Poll = { id: number; title: string };
const polls = await req.cache.get<Poll[]>("/polls/recent:v1");
// Poll[] | null. The type argument does not validate stored data.Without a type argument, reads return unknown. The GoribuCache type is available from goribu/server.
Keys and scope
Keys are valid Unicode strings starting with /, such as /polls/42. They are literal identifiers and need not identify a route: /polls/../42, /42, /42#summary, and /42?full remain distinct.
Entries are shared across requests to the same origin within a data center. Different schemes, hosts, or ports have separate entries. Goribu uses a named cache separate from the default HTTP cache; storing a value does not publish it at that URL or change response cache headers.
Users on the same origin share keys. Include the user or tenant identifier when caching personalized data, and authorize the request before reading it. Use versioned keys when changing the stored JSON shape across deployments.
Refresh after mutations
After a successful database mutation, delete the affected entries or replace a local entry with the updated value you already have:
// After the database mutation succeeds:
await req.cache.delete("/polls/recent:v1", `/polls/${id}`);
// Or, when the mutation already returned the updated record:
await req.cache.set(`/polls/${poll.id}`, poll, { ttl: 300 });Keep the authoritative mutation result for confirmation. Concurrent cache fills can race with writes or deletions; the cache does not coordinate requests or provide transactions.
Cloudflare's Cache API is local to the data center executing the Worker. An update there does not invalidate another location's copy. Choose a TTL that matches the staleness your app can tolerate. Neither this helper nor KV provides global strong consistency.
Smart Placement
Smart Placement can improve cache reuse and refresh behavior for localized apps by concentrating more reads and mutations near the app's data. Requests reaching the same execution location share its local cache and updates. Other locations still depend on their own updates or TTL expiry; placement does not guarantee that every request reaches one location. Configure it through Cloudflare's Worker placement settings.
Background writes and failures
To return a response without waiting for a cache fill, register the write explicitly with req.waitUntil:
req.waitUntil(
req.cache.set("/polls/recent:v1", polls, { ttl: 300 }),
);Ordinary calls never schedule background work automatically. Await a deletion when the handler should wait for its local attempt to complete.
Invalid keys, values, or options reject with an actionable [Goribu] error. Every key in a deletion batch is validated before deletion starts. Platform failures and unreadable cached JSON produce a warning and become a miss or skipped write/delete, allowing the handler to continue.
In a runtime without the Cache API, reads miss and writes/deletes do nothing, but argument validation still runs. Local development runs in workerd and has a local Cache API. Use that runtime for tests that need actual storage.
Deployment support
| Deployment | Cache API behavior |
|---|---|
| Custom domain or route | Cloudflare documents support; Goribu's custom-domain fixture passed live checks. |
workers.dev | Goribu's named cache passed live checks on the tested deployment, despite the no-op restriction in Cloudflare's R2 guide. |
| Dashboard editor and Playground previews | Cloudflare documents that cache operations have no impact. |
| Worker fronted by Cloudflare Access | Cloudflare documents the Cache API as unavailable. |
Live checks on October 2, 2026 confirmed hits, literal keys, origin isolation, deletion, and TTL behavior on one custom domain and its workers.dev origin. This does not establish support for every deployment, previews, or Access. The discrepancy with Cloudflare's R2 guide has no established explanation. The Cache API reference documents the preview and Access restrictions.
Verify actual hits where caching matters. Unsupported deployments can accept writes without retaining them and return misses without throwing, so neither a resolved set nor an absence of warnings proves a hit. Cloudflare's separate Workers Cache product does not establish support for the Cache API used here.
Static pages
When a GET handler is explicitly prerendered, Goribu bypasses cache reads and writes so the build reads source data. Generated HTML and navigation props stay at that build-time snapshot. Cache expiry or deletion does not refresh static output; rebuild and deploy to update it. See pre-rendered pages.