Skip to content

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 TTL
await 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");
});

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 flag
await flags.put("dark-mode", { enabled: true, rollout: 0.5 });
// Check a flag
const flag = await flags.get("dark-mode");
if (flag.value.enabled) {
enableDarkMode();
}
// List all flags
const keys = await flags.listKeys();
for (const key of keys) {
const entry = await flags.get(key);
console.log(`${key}: ${JSON.stringify(entry.value)}`);
}

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 hour
await kv.createBucket({ name: "sessions", ttlSeconds: 3600 });
const sessions = kv.bucket("sessions");
// Store session data
await sessions.put("user:abc-123", {
userId: "abc-123",
role: "admin",
lastActiveAt: new Date().toISOString(),
});
// Retrieve session
try {
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 logout
await sessions.delete("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 counter
await 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}`);