Quickback Docs

KV Storage

Cloudflare Workers KV is a global key-value store optimized for read-heavy workloads. Quickback uses KV for session storage, caching, and fast lookups.

What is KV?

Workers KV is a distributed key-value store:

  • Global distribution - Data replicated to 300+ edge locations
  • Low-latency reads - Cached at the edge, sub-millisecond access
  • Eventually consistent - Writes propagate globally within 60 seconds
  • Simple API - Get, put, delete, list

Use Cases in Quickback

Use CaseDescription
SessionsBetter Auth stores sessions in KV for fast validation
CacheCache expensive database queries or API responses
Rate LimitingTrack request counts per user/IP
Feature FlagsStore configuration that changes infrequently

Namespace Setup

Create a KV namespace via Wrangler:

# Create namespace
wrangler kv namespace create "SESSIONS"

# Output:
# Add the following to your wrangler.toml:
# [[kv_namespaces]]
# binding = "SESSIONS"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Add to wrangler.toml:

[[kv_namespaces]]
binding = "SESSIONS"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[[kv_namespaces]]
binding = "CACHE"
id = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

Reading and Writing Values

Basic Operations

// Write a value
await env.CACHE.put("user:123", JSON.stringify({ name: "Alice" }));

// Read a value
const data = await env.CACHE.get("user:123");
const user = data ? JSON.parse(data) : null;

// Delete a value
await env.CACHE.delete("user:123");

// Check if key exists
const exists = await env.CACHE.get("user:123") !== null;

With Metadata

KV supports metadata attached to keys:

// Write with metadata
await env.CACHE.put("user:123", JSON.stringify(userData), {
  metadata: { updatedAt: Date.now(), version: 1 },
});

// Read with metadata
const { value, metadata } = await env.CACHE.getWithMetadata("user:123");

List Keys

// List all keys with prefix
const list = await env.CACHE.list({ prefix: "user:" });

for (const key of list.keys) {
  console.log(key.name, key.metadata);
}

// Paginate through results
let cursor = undefined;
do {
  const result = await env.CACHE.list({ prefix: "user:", cursor });
  // Process result.keys
  cursor = result.cursor;
} while (cursor);

Expiration and TTL

Set automatic expiration on keys:

// Expire in 1 hour (3600 seconds)
await env.CACHE.put("session:abc", sessionData, {
  expirationTtl: 3600,
});

// Expire at specific timestamp
await env.CACHE.put("session:abc", sessionData, {
  expiration: Math.floor(Date.now() / 1000) + 3600,
});

Common TTL patterns:

Use CaseTTL
Session tokens24 hours (86400)
API cache5 minutes (300)
Rate limit counters1 minute (60)
Feature flagsNo expiration

Session Storage

Better Auth uses KV for session storage in Cloudflare deployments:

// Quickback configures this automatically
auth: defineAuth("better-auth", {
  session: {
    storage: "kv", // Uses SESSIONS namespace
  },
}),

Sessions are stored as:

  • Key: session:{sessionId}
  • Value: Serialized session object
  • TTL: Configured session expiration

Caching Patterns

Cache-Aside Pattern

async function getUser(userId: string) {
  // Check cache first
  const cached = await env.CACHE.get(`user:${userId}`);
  if (cached) {
    return JSON.parse(cached);
  }

  // Fetch from database
  const user = await db.select().from(users).where(eq(users.id, userId));

  // Store in cache
  await env.CACHE.put(`user:${userId}`, JSON.stringify(user), {
    expirationTtl: 300, // 5 minutes
  });

  return user;
}

Cache Invalidation

// Invalidate on update
async function updateUser(userId: string, data: UserUpdate) {
  await db.update(users).set(data).where(eq(users.id, userId));
  await env.CACHE.delete(`user:${userId}`);
}

wrangler.toml Reference

# Production namespace
[[kv_namespaces]]
binding = "SESSIONS"
id = "abc123..."

# Preview namespace (for wrangler dev)
[[kv_namespaces]]
binding = "SESSIONS"
id = "def456..."
preview_id = "ghi789..."

Limitations

  • Value size - Maximum 25 MB per value
  • Key size - Maximum 512 bytes
  • Write limits - 1 write per second per key
  • Eventual consistency - Changes may take up to 60 seconds to propagate
  • Not for hot writes - Use Durable Objects for high-write scenarios

KV is optimized for read-heavy workloads. For write-heavy use cases or strong consistency, consider Durable Objects.

On this page