Skip to content

Key Value (KV)

Overview

The Raindrop framework provides a key-value storage interface for storing and retrieving data by unique keys. KV stores are ideal for caching, session storage, configuration management, and simple data persistence.

The KV interface supports multiple data types including text, JSON objects, binary data, and streams. It also provides metadata support, expiration controls, and listing capabilities for managing your stored data.

Creating a KV Store

Add a KV store to your Raindrop project by defining it in your application manifest:

application "demo-app" {
kv_store "demo-cache" {}
}

After running raindrop build generate, the store becomes available as env.DEMO_CACHE in your services and actors.

Core Methods

put(key, value, options?)

Stores a value with the given key.

// Store text
await env.DEMO_CACHE.put("user:123", "John Doe");
// Store with expiration (TTL in seconds)
await env.DEMO_CACHE.put("session:abc", sessionData, {
expirationTtl: 3600 // 1 hour
});
// Store with metadata
await env.DEMO_CACHE.put("file:456", fileData, {
metadata: { contentType: "image/png", size: 1024 }
});

Options:

  • expirationTtl: Expire after N seconds (minimum 60 seconds)
  • expiration: Expire at Unix timestamp (must be in the future)
  • metadata: Custom metadata object

get(key, type?)

Retrieves a value by key with optional type conversion.

// Get as text (default)
const value = await env.DEMO_CACHE.get("user:123");
// Get as JSON
const data = await env.DEMO_CACHE.get("config:settings", "json");
// Get as binary data
const buffer = await env.DEMO_CACHE.get("file:456", "arrayBuffer");
// Get as stream
const stream = await env.DEMO_CACHE.get("large:file", "stream");

Types:

  • "text" (default): Returns string
  • "json": Parses JSON and returns object
  • "arrayBuffer": Returns ArrayBuffer
  • "stream": Returns ReadableStream

Returns null if key doesn’t exist or has expired.

getWithMetadata(key, type?)

Retrieves value along with metadata and cache information.

const result = await env.DEMO_CACHE.getWithMetadata("file:456", "json");
console.log(result.value); // The stored value
console.log(result.metadata); // Custom metadata
console.log(result.cacheStatus); // Cache status string or null

Response:

  • value: The stored value or null if not found
  • metadata: Custom metadata object or null
  • cacheStatus: Cache status string or null

list(options?)

Lists stored keys with optional filtering and pagination.

// List all keys
const allKeys = await env.DEMO_CACHE.list();
// List with prefix filter
const userKeys = await env.DEMO_CACHE.list({ prefix: "user:" });
// List with limit and pagination
const batch = await env.DEMO_CACHE.list({
prefix: "session:",
limit: 100,
cursor: "..." // from previous call
});

Options:

  • prefix: Filter keys by prefix
  • limit: Maximum number of keys to return
  • cursor: Pagination cursor from previous call

Response:

  • keys: Array of key objects with name, expiration, metadata
  • list_complete: Boolean indicating if more results exist
  • cursor: Use for next page (if list_complete is false)

delete(key)

Removes a key from storage.

await env.DEMO_CACHE.delete("user:123");

The operation is idempotent - deleting a non-existent key doesn’t throw an error.

Data Types

Text Storage

Store and retrieve simple string values like configuration settings, user preferences, or short text data.

await env.DEMO_CACHE.put("config:theme", "dark");
const theme = await env.DEMO_CACHE.get("config:theme"); // "dark"

JSON Storage

Store complex objects and data structures by serializing them as JSON strings.

const user = { id: 123, name: "John", active: true };
await env.DEMO_CACHE.put("user:123", JSON.stringify(user));
const userData = await env.DEMO_CACHE.get("user:123", "json");

Binary Storage

Store binary data like images, files, or any raw bytes using ArrayBuffer.

const imageData = new ArrayBuffer(1024);
await env.DEMO_CACHE.put("image:456", imageData);
const retrievedData = await env.DEMO_CACHE.get("image:456", "arrayBuffer");

Stream Storage

Store large data as streams for memory-efficient handling of big files or continuous data.

const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("chunk1"));
controller.enqueue(new TextEncoder().encode("chunk2"));
controller.close();
}
});
await env.DEMO_CACHE.put("file:789", stream);
const retrievedStream = await env.DEMO_CACHE.get("file:789", "stream");

Examples

Basic Usage

Simple session management using KV storage with automatic expiration.

export default class extends Service<Env> {
async fetch(request: Request): Promise<Response> {
// Store user session
const sessionId = "abc123";
await this.env.DEMO_CACHE.put(`session:${sessionId}`, JSON.stringify({
userId: 123,
loginTime: new Date().toISOString()
}), {
expirationTtl: 24 * 60 * 60 // 24 hours
});
// Retrieve session
const sessionData = await this.env.DEMO_CACHE.get(`session:${sessionId}`, "json");
if (!sessionData) {
return new Response("Session not found", { status: 401 });
}
return new Response(`Welcome user ${sessionData.userId}`);
}
}

Cache Pattern

Implement read-through caching to reduce database load and improve response times.

async getUser(userId: string) {
// Check cache first
let user = await this.env.DEMO_CACHE.get(`user:${userId}`, "json");
if (!user) {
// Cache miss - fetch from database
user = await this.database.getUser(userId);
// Cache for 5 minutes
await this.env.DEMO_CACHE.put(`user:${userId}`, JSON.stringify(user), {
expirationTtl: 300
});
}
return user;
}

Cleanup Pattern

Manually clean up expired entries by listing and filtering based on expiration times.

async cleanupExpiredSessions() {
const sessions = await this.env.DEMO_CACHE.list({ prefix: "session:" });
const now = Date.now() / 1000;
for (const key of sessions.keys) {
if (key.expiration && key.expiration < now) {
await this.env.DEMO_CACHE.delete(key.name);
}
}
}