Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/kv/patterns.md
1# KV Patterns & Best Practices23## Multi-Tier Caching45```typescript6// Memory → KV → Origin (3-tier cache)7const memoryCache = new Map<string, { data: any; expires: number }>();89async function getCached(env: Env, key: string): Promise<any> {10const now = Date.now();1112// L1: Memory cache (fastest)13const cached = memoryCache.get(key);14if (cached && cached.expires > now) {15return cached.data;16}1718// L2: KV cache (fast)19const kvValue = await env.CACHE.get(key, "json");20if (kvValue) {21memoryCache.set(key, { data: kvValue, expires: now + 60000 }); // 1min in memory22return kvValue;23}2425// L3: Origin (slow)26const origin = await fetch(`https://api.example.com/${key}`).then(r => r.json());2728// Backfill caches29await env.CACHE.put(key, JSON.stringify(origin), { expirationTtl: 300 }); // 5min in KV30memoryCache.set(key, { data: origin, expires: now + 60000 });3132return origin;33}34```3536## API Response Caching3738```typescript39async function getCachedData(env: Env, key: string, fetcher: () => Promise<any>): Promise<any> {40const cached = await env.MY_KV.get(key, "json");41if (cached) return cached;4243const data = await fetcher();44await env.MY_KV.put(key, JSON.stringify(data), { expirationTtl: 300 });45return data;46}4748const apiData = await getCachedData(49env,50"cache:users",51() => fetch("https://api.example.com/users").then(r => r.json())52);53```5455## Session Management5657```typescript58interface Session { userId: string; expiresAt: number; }5960async function createSession(env: Env, userId: string): Promise<string> {61const sessionId = crypto.randomUUID();62const expiresAt = Date.now() + (24 * 60 * 60 * 1000);6364await env.SESSIONS.put(65`session:${sessionId}`,66JSON.stringify({ userId, expiresAt }),67{ expirationTtl: 86400, metadata: { createdAt: Date.now() } }68);6970return sessionId;71}7273async function getSession(env: Env, sessionId: string): Promise<Session | null> {74const data = await env.SESSIONS.get<Session>(`session:${sessionId}`, "json");75if (!data || data.expiresAt < Date.now()) return null;76return data;77}78```7980## Coalesce Cold Keys8182```typescript83// ❌ BAD: Many individual keys84await env.KV.put("user:123:name", "John");85await env.KV.put("user:123:email", "[email protected]");8687// ✅ GOOD: Single coalesced object88await env.USERS.put("user:123:profile", JSON.stringify({89name: "John",90email: "[email protected]",91role: "admin"92}));9394// Benefits: Hot key cache, single read, reduced operations95// Trade-off: Harder to update individual fields96```9798## Prefix-Based Namespacing99100```typescript101// Logical partitioning within single namespace102const PREFIXES = {103users: "user:",104sessions: "session:",105cache: "cache:",106features: "feature:"107} as const;108109// Write with prefix110async function setUser(env: Env, id: string, data: any) {111await env.KV.put(`${PREFIXES.users}${id}`, JSON.stringify(data));112}113114// Read with prefix115async function getUser(env: Env, id: string) {116return await env.KV.get(`${PREFIXES.users}${id}`, "json");117}118119// List by prefix120async function listUserIds(env: Env): Promise<string[]> {121const result = await env.KV.list({ prefix: PREFIXES.users });122return result.keys.map(k => k.name.replace(PREFIXES.users, ""));123}124125// Example hierarchy126"user:123:profile"127"user:123:settings"128"cache:api:users"129"session:abc-def"130"feature:flags:beta"131```132133## Metadata Versioning134135```typescript136interface VersionedData {137version: number;138data: any;139}140141async function migrateIfNeeded(env: Env, key: string) {142const result = await env.DATA.getWithMetadata(key, "json");143144if (!result.value) return null;145146const currentVersion = result.metadata?.version || 1;147const targetVersion = 2;148149if (currentVersion < targetVersion) {150// Migrate data format151const migrated = migrate(result.value, currentVersion, targetVersion);152153// Store with new version154await env.DATA.put(key, JSON.stringify(migrated), {155metadata: { version: targetVersion, migratedAt: Date.now() }156});157158return migrated;159}160161return result.value;162}163164function migrate(data: any, from: number, to: number): any {165if (from === 1 && to === 2) {166// V1 → V2: Rename field167return { ...data, userName: data.name };168}169return data;170}171```172173## Error Boundary Pattern174175```typescript176// Resilient get with fallback177async function resilientGet<T>(178env: Env,179key: string,180fallback: T181): Promise<T> {182try {183const value = await env.KV.get<T>(key, "json");184return value ?? fallback;185} catch (err) {186console.error(`KV error for ${key}:`, err);187return fallback;188}189}190191// Usage192const config = await resilientGet(env, "config:app", {193theme: "light",194maxItems: 10195});196```197