Patterns
Common Patterns
Practical recipes for common KV Store use cases.
Caching with TTL
Use TTL buckets to cache computed or fetched data. Keys auto-expire after the configured duration, so stale data is cleaned up automatically. On a cache miss, recompute the value and store it for future reads.
import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123" });const kv = client.kv();
// Create a bucket with 5-minute TTLawait kv.createBucket({ name: "cache", ttlSeconds: 300 });const cache = kv.bucket("cache");
async function getCached(key: string, compute: () => Promise<unknown>) { try { const entry = await cache.get(key); return entry.value; } catch { // Cache miss — compute and store const value = await compute(); await cache.put(key, value); return value; }}
const profile = await getCached("user:123:profile", async () => { return fetchUserProfile("123");});import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });const kv = ironflow.kv();
// Create a bucket with 5-minute TTLawait kv.createBucket({ name: "cache", ttlSeconds: 300 });const cache = kv.bucket("cache");
async function getCached(key: string, compute: () => Promise<unknown>) { try { const entry = await cache.get(key); return entry.value; } catch { // Cache miss — compute and store const value = await compute(); await cache.put(key, value); return value; }}
const profile = await getCached("user:123:profile", async () => { return fetchUserProfile("123");});client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123",})kv := client.KV()
// Create a bucket with 5-minute TTLkv.CreateBucket(ctx, ironflow.BucketConfig{ Name: "cache", TTL: 5 * time.Minute,})cache := kv.Bucket("cache")
// Try cache firstentry, err := cache.Get(ctx, "user:123:profile")if err != nil { // Cache miss — compute and store data := computeProfile(ctx, "123") cache.Put(ctx, "user:123:profile", data)}Feature Flags
Store feature flags as JSON values in a dedicated bucket. Read flags at runtime to control application behavior. Updates take effect immediately without redeployment.
import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123" });const flags = client.kv().bucket("feature-flags");
// Set a flagawait flags.put("dark-mode", { enabled: true, rollout: 0.5 });
// Check a flagconst flag = await flags.get("dark-mode");if (flag.value.enabled) { enableDarkMode();}
// List all flagsconst keys = await flags.listKeys();for (const key of keys) { const entry = await flags.get(key); console.log(`${key}: ${JSON.stringify(entry.value)}`);}import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });const flags = ironflow.kv().bucket("feature-flags");
// Set a flagawait flags.put("dark-mode", { enabled: true, rollout: 0.5 });
// Check a flagconst flag = await flags.get("dark-mode");if (flag.value.enabled) { enableDarkMode();}
// Watch for flag changes in real timeflags.watch({ onUpdate: (event) => { console.log(`Flag changed: ${event.key}`); applyFlag(event.key, JSON.parse(event.value)); },});client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123",})flags := client.KV().Bucket("feature-flags")
// Set a flagflags.Put(ctx, "dark-mode", []byte(`{"enabled":true,"rollout":0.5}`))
// Check a flagentry, err := flags.Get(ctx, "dark-mode")if err != nil { log.Fatal(err)}
var flag struct { Enabled bool `json:"enabled"` Rollout float64 `json:"rollout"`}json.Unmarshal(entry.Value, &flag)
if flag.Enabled { enableDarkMode()}Session State
Store per-user session data in a TTL bucket so sessions expire automatically. Use key naming conventions like user:<id> to organize entries.
import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123" });const kv = client.kv();
// Sessions expire after 1 hourawait kv.createBucket({ name: "sessions", ttlSeconds: 3600 });const sessions = kv.bucket("sessions");
// Store session dataawait sessions.put("user:abc-123", { userId: "abc-123", role: "admin", lastActiveAt: new Date().toISOString(),});
// Retrieve sessiontry { const session = await sessions.get("user:abc-123"); console.log(session.value.role); // "admin"} catch { // Session expired or doesn't exist redirectToLogin();}
// Refresh session on activity (resets TTL)await sessions.put("user:abc-123", { ...existingSession, lastActiveAt: new Date().toISOString(),});
// Explicit logoutawait sessions.delete("user:abc-123");import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });const kv = ironflow.kv();
// Sessions expire after 1 hourawait kv.createBucket({ name: "sessions", ttlSeconds: 3600 });const sessions = kv.bucket("sessions");
// Store session dataawait sessions.put("user:abc-123", { userId: "abc-123", role: "admin", lastActiveAt: new Date().toISOString(),});
// Retrieve sessiontry { const session = await sessions.get("user:abc-123"); console.log(session.value.role); // "admin"} catch { // Session expired or doesn't exist redirectToLogin();}
// Explicit logoutawait sessions.delete("user:abc-123");client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123",})kv := client.KV()
// Sessions expire after 1 hourkv.CreateBucket(ctx, ironflow.BucketConfig{ Name: "sessions", TTL: time.Hour,})sessions := kv.Bucket("sessions")
// Store session datadata, _ := json.Marshal(map[string]any{ "userId": "abc-123", "role": "admin", "lastActiveAt": time.Now().Format(time.RFC3339),})sessions.Put(ctx, "user:abc-123", data)
// Retrieve sessionentry, err := sessions.Get(ctx, "user:abc-123")if err != nil { // Session expired or doesn't exist redirectToLogin() return}
var session map[string]anyjson.Unmarshal(entry.Value, &session)fmt.Println(session["role"]) // "admin"
// Explicit logoutsessions.Delete(ctx, "user:abc-123")Optimistic Locking
Use compare-and-swap (update) for concurrent-safe modifications. Read the current value and its revision, apply your change, then write back with the original revision. If another writer modified the key in between, the update fails and you retry.
import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123" });const counters = client.kv().bucket("counters");
// Initialize counterawait counters.put("page-views", 0);
async function incrementCounter(key: string): Promise<number> { while (true) { const entry = await counters.get(key); const count = (entry.value as number) + 1; try { await counters.update(key, count, entry.revision); return count; } catch { // Revision mismatch — another writer won, retry continue; } }}
const newCount = await incrementCounter("page-views");console.log(`Page views: ${newCount}`);import { ironflow } from "@ironflow/browser";
ironflow.configure({ serverUrl: "http://localhost:9123" });const counters = ironflow.kv().bucket("counters");
// Initialize counterawait counters.put("page-views", 0);
async function incrementCounter(key: string): Promise<number> { while (true) { const entry = await counters.get(key); const count = (entry.value as number) + 1; try { await counters.update(key, count, entry.revision); return count; } catch { // Revision mismatch — another writer won, retry continue; } }}
const newCount = await incrementCounter("page-views");console.log(`Page views: ${newCount}`);client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123",})counters := client.KV().Bucket("counters")
// Initialize countercounters.Put(ctx, "page-views", []byte("0"))
func incrementCounter(ctx context.Context, bucket *ironflow.KVBucket, key string) (int, error) { for { entry, err := bucket.Get(ctx, key) if err != nil { return 0, err }
count, _ := strconv.Atoi(string(entry.Value)) count++
_, err = bucket.Update(ctx, key, []byte(strconv.Itoa(count)), entry.Revision) if err != nil { // Revision mismatch — another writer won, retry continue } return count, nil }}
newCount, _ := incrementCounter(ctx, counters, "page-views")fmt.Printf("Page views: %d\n", newCount)