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:
# 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 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:
// 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.