# 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 Case | Description |
|----------|-------------|
| **Sessions** | Better Auth stores sessions in KV for fast validation |
| **Cache** | Cache expensive database queries or API responses |
| **Rate Limiting** | Track request counts per user/IP |
| **Feature Flags** | Store configuration that changes infrequently |

## Namespace Setup

Create a KV namespace via Wrangler:

```bash
# 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:

```typescript
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

```ts
// 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:

```ts
// 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

```ts
// 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:

```ts
// 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 Case | TTL |
|----------|-----|
| Session tokens | 24 hours (86400) |
| API cache | 5 minutes (300) |
| Rate limit counters | 1 minute (60) |
| Feature flags | No expiration |

## Session Storage

Better Auth uses KV for session storage in Cloudflare deployments:

```ts
// 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

```ts
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

```ts
// 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

```toml
# 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](/stack/realtime/durable-objects).
