# KV Patterns & Best Practices ## Multi-Tier Caching ```typescript // Memory → KV → Origin (3-tier cache) const memoryCache = new Map(); async function getCached(env: Env, key: string): Promise { const now = Date.now(); // L1: Memory cache (fastest) const cached = memoryCache.get(key); if (cached && cached.expires > now) { return cached.data; } // L2: KV cache (fast) const kvValue = await env.CACHE.get(key, "json"); if (kvValue) { memoryCache.set(key, { data: kvValue, expires: now + 60000 }); // 1min in memory return kvValue; } // L3: Origin (slow) const origin = await fetch(`https://api.example.com/${key}`).then(r => r.json()); // Backfill caches await env.CACHE.put(key, JSON.stringify(origin), { expirationTtl: 300 }); // 5min in KV memoryCache.set(key, { data: origin, expires: now + 60000 }); return origin; } ``` ## API Response Caching ```typescript async function getCachedData(env: Env, key: string, fetcher: () => Promise): Promise { const cached = await env.MY_KV.get(key, "json"); if (cached) return cached; const data = await fetcher(); await env.MY_KV.put(key, JSON.stringify(data), { expirationTtl: 300 }); return data; } const apiData = await getCachedData( env, "cache:users", () => fetch("https://api.example.com/users").then(r => r.json()) ); ``` ## Session Management ```typescript interface Session { userId: string; expiresAt: number; } async function createSession(env: Env, userId: string): Promise { const sessionId = crypto.randomUUID(); const expiresAt = Date.now() + (24 * 60 * 60 * 1000); await env.SESSIONS.put( `session:${sessionId}`, JSON.stringify({ userId, expiresAt }), { expirationTtl: 86400, metadata: { createdAt: Date.now() } } ); return sessionId; } async function getSession(env: Env, sessionId: string): Promise { const data = await env.SESSIONS.get(`session:${sessionId}`, "json"); if (!data || data.expiresAt < Date.now()) return null; return data; } ``` ## Coalesce Cold Keys ```typescript // ❌ BAD: Many individual keys await env.KV.put("user:123:name", "John"); await env.KV.put("user:123:email", "john@example.com"); // ✅ GOOD: Single coalesced object await env.USERS.put("user:123:profile", JSON.stringify({ name: "John", email: "john@example.com", role: "admin" })); // Benefits: Hot key cache, single read, reduced operations // Trade-off: Harder to update individual fields ``` ## Prefix-Based Namespacing ```typescript // Logical partitioning within single namespace const PREFIXES = { users: "user:", sessions: "session:", cache: "cache:", features: "feature:" } as const; // Write with prefix async function setUser(env: Env, id: string, data: any) { await env.KV.put(`${PREFIXES.users}${id}`, JSON.stringify(data)); } // Read with prefix async function getUser(env: Env, id: string) { return await env.KV.get(`${PREFIXES.users}${id}`, "json"); } // List by prefix async function listUserIds(env: Env): Promise { const result = await env.KV.list({ prefix: PREFIXES.users }); return result.keys.map(k => k.name.replace(PREFIXES.users, "")); } // Example hierarchy "user:123:profile" "user:123:settings" "cache:api:users" "session:abc-def" "feature:flags:beta" ``` ## Metadata Versioning ```typescript interface VersionedData { version: number; data: any; } async function migrateIfNeeded(env: Env, key: string) { const result = await env.DATA.getWithMetadata(key, "json"); if (!result.value) return null; const currentVersion = result.metadata?.version || 1; const targetVersion = 2; if (currentVersion < targetVersion) { // Migrate data format const migrated = migrate(result.value, currentVersion, targetVersion); // Store with new version await env.DATA.put(key, JSON.stringify(migrated), { metadata: { version: targetVersion, migratedAt: Date.now() } }); return migrated; } return result.value; } function migrate(data: any, from: number, to: number): any { if (from === 1 && to === 2) { // V1 → V2: Rename field return { ...data, userName: data.name }; } return data; } ``` ## Error Boundary Pattern ```typescript // Resilient get with fallback async function resilientGet( env: Env, key: string, fallback: T ): Promise { try { const value = await env.KV.get(key, "json"); return value ?? fallback; } catch (err) { console.error(`KV error for ${key}:`, err); return fallback; } } // Usage const config = await resilientGet(env, "config:app", { theme: "light", maxItems: 10 }); ```