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 "KV"

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

Setting the namespace ID in config

To have the compiler generate wrangler.toml with your KV namespace ID, set it in your provider config:

providers: {
  storage: defineStorage("kv", {
    namespaceId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  }),
}

The namespaceId can also be set as kvId on the auth provider config, since KV is primarily used for auth session storage. The compiler checks both locations.

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