Pokédex at the Edge
A Cloudflare Workers reverse proxy for Pokémon lookups. No database, no origin server to maintain — just a wildcard route, Cloudflare KV as the entire cache layer, and PokeAPI as the only source of truth.
- Project type
- Reverse proxy & edge cache (Cloudflare Worker)
- Role
- Solo developer — architecture, caching design, debugging
- Created
- November 2026
- Status
- Live — reachable at kalob.io/pokemon/@<name>
- Trigger
- GET request to a wildcard Worker route
- Data source
- PokeAPI (pokeapi.co) — no origin database
// 01 — The problem
Every lookup is slow and repetitive if you hit the origin every time.
Answering kalob.io/pokemon/@charizard means resolving a name to a Pokémon ID, fetching that Pokémon's record, its species record, and its evolution chain — up to four separate calls to PokeAPI for one page. Doing that on every single visit is slow, and it leans on someone else's free API far more than it needs to.
The brief going in was simple: build the whole thing as a Cloudflare Worker, store results in Cloudflare KV instead of standing up a database, and cache aggressively — seven days per entry — so repeat lookups come straight from the edge.
Try it
// 02 — The approach
Five stages. The cache is the architecture.
There's no app server behind this and nothing resembling a traditional backend. A Worker intercepts the request, Cloudflare KV remembers the answer, and PokeAPI only gets called on a true cache miss.
-
01
Route the slug straight to one Worker
A single Worker route, kalob.io/pokemon/@*, catches every lookup. The part after @ is the Pokémon slug — eevee, charizard, meowstic-female-mega — read straight out of the URL path. No query string, no form, no origin server to fall back to.
-
02
Check Cloudflare KV before touching PokeAPI
Every request asks a KV namespace for a previously cached page by slug first. A hit skips PokeAPI entirely and serves the stored HTML straight back, stamped with an X-Pokemon-Cache: HIT header.
-
03
Resolve name to number, once
On a miss, the Worker loads PokeAPI's full name-to-URL index, then the Pokémon's own record, its species record, and its evolution chain — each cached as its own independent KV entry, not bundled into one blob.
-
04
Render sprites and the evolution tree
Official artwork, front/back/shiny sprites, and the full species evolution chain — branches and all, for Pokémon like Eevee — get rendered into one static HTML page and returned directly, no client-side JavaScript required.
-
05
Expire after seven days, rebuild on its own
Every cached entry — the rendered page, the shared index, each species and evolution chain — expires seven days after it's written and silently rebuilds itself on the next request that needs it. No cron job, no manual purge, no server that has to stay running to make that happen.
// 03 — Technical insights
Why Cloudflare KV instead of a database
This data doesn't need a database's guarantees. Pokémon sprites and evolution chains barely change, there's no relational querying to do, and the only access pattern is "fetch by a known key." Cloudflare KV — a key-value store built into the same account as the Worker — fits that shape exactly: no schema to design, no connection pool to manage, no server to provision, and it's replicated globally so a cached entry written near one edge location is readable from the rest.
The cache key is deliberately layered instead of one flat blob per page: the shared name index, each species record, and each evolution chain are all cached independently from the final rendered page. That's what lets two different slugs pointing at the same species — Meowstic and its female form — reuse each other's cached lookups instead of duplicating them.
Every layer — the page, the index, each species, each evolution chain — calls this same helper with its own key. The first deploy still shipped a bug: PokeAPI's own fetch calls used redirect: "error", which looks reasonable coming from browser JavaScript but throws inside a Worker, since the edge runtime only implements follow and manual. Live log streaming with wrangler tail surfaced the exact TypeError within a minute of reloading the page, and switching to redirect: "manual" plus a response.ok check fixed it without touching anything KV-related.
// 04 — Results
A warm lookup never leaves Cloudflare's edge.
| Measure | Result |
|---|---|
| Cache hit | Zero calls to PokeAPI — served straight out of Cloudflare KV at the edge closest to the visitor |
| Cache miss | Up to four PokeAPI calls (index, Pokémon, species, evolution chain), each individually cached for next time |
| Shared records | Different forms of the same species — Meowstic and Meowstic (female) — reuse the same cached species and evolution-chain entries |
| Cache lifetime | 7 days per entry, auto-expiring, no manual invalidation required |
| Backing infrastructure | 0 servers, 0 databases — one Worker and one KV namespace, both inside the same Cloudflare account as kalob.io |
// 05 — What I learned
KV is a cache, not a database — and that's the whole point
This project never needed relations, queries, or a schema. It needed fast lookups by a known key with an expiry date. Cloudflare KV — a globally replicated, eventually consistent key-value store — is a better fit for that job than a database would have been, and there's nothing to provision or patch.
Cache the ingredients, not just the finished page
Caching only the final rendered HTML would have meant every new Pokémon form re-fetched the same shared index, species, and evolution data. Caching the index, species record, and evolution chain as their own KV entries means two unrelated slugs that point at the same species — any Meowstic form, for instance — reuse each other's work.
"redirect: error" doesn't exist at the edge
The first deploy threw a 502 on every request. Live log streaming via wrangler tail pointed straight at it: Workers' fetch() only accepts follow or manual for redirect — error isn't implemented, because it doesn't make sense at the edge. Switching to manual and checking response.ok fixed it in one line.
// Technology used
| Layer | Technology |
|---|---|
| Compute | Cloudflare Workers |
| Cache / storage | Cloudflare KV (no database) |
| Routing | Cloudflare Worker route — kalob.io/pokemon/@* |
| Data source | PokeAPI (pokeapi.co) |
| Rendering | Hand-rolled HTML, zero client-side dependencies |
| Observability | wrangler tail live log streaming |
All Pokémon names, sprites, species, and evolution data come from PokeAPI, a free, open, community-run REST API. This project is not affiliated with PokeAPI, Nintendo, Game Freak, or The Pokémon Company — it's a caching layer in front of their data, built as a technical exercise.