Virke KV
Fast key-value storage at the edge. Virke KV is a thin wrapper over Fastly KV Store, namespaced per project.
Performance
| Operation | Latency |
|---|---|
| Read (warm POP) | 4–10ms |
| Write | ~450ms |
| List keys | 10–30ms |
| Delete | ~450ms |
CLI Usage
Set a value
virke kv set user:123 '{"name": "Alice", "email": "alice@example.com"}'
With a TTL (time-to-live) in seconds:
virke kv set session:abc token-value --ttl 3600
Get a value
virke kv get user:123
List keys
virke kv list
virke kv list --prefix user:
Delete a key
virke kv delete user:123
SDK Usage (from Virke Run)
With Hono
import { Hono } from "hono";
import { virke } from "@virke/runtime/hono";
const { fire, middleware, Bindings } = virke({ kv: "my-app-kv" });
const app = new Hono<{ Bindings: typeof Bindings }>();
app.use("*", middleware);
// String values
app.get("/session/:id", async (c) => {
const session = await c.env.kv.get(
`session:${c.req.param("id")}`
);
if (!session) return c.json({ error: "Not found" }, 404);
return c.text(session);
});
// JSON values
app.get("/user/:id", async (c) => {
const user = await c.env.kv.getJson(`user:${c.req.param("id")}`);
if (!user) return c.json({ error: "Not found" }, 404);
return c.json(user);
});
// Write with TTL
app.post("/session", async (c) => {
const { userId, token } = await c.req.json();
await c.env.kv.set(`session:${token}`, userId, { ttl: 3600 });
return c.json({ ok: true });
});
// List keys with pagination
app.get("/keys", async (c) => {
const prefix = c.req.query("prefix");
const cursor = c.req.query("cursor");
const result = await c.env.kv.list({ prefix, limit: 100, cursor });
return c.json(result);
});
fire(app);
KV binding API
interface VirkeKV {
get(key: string): Promise<string | null>;
getJson<T = unknown>(key: string): Promise<T | null>;
getBytes(key: string): Promise<ArrayBuffer | null>;
getWithMetadata(key: string): Promise<{ value: string | null; generation: number }>;
set(key: string, value: string | ArrayBuffer, options?: { ttl?: number; generation?: number }): Promise<void>;
setJson(key: string, value: unknown, options?: { ttl?: number; generation?: number }): Promise<void>;
delete(key: string): Promise<void>;
list(options?: {
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{ keys: string[]; cursor: string | undefined }>;
}
Use Cases
Session storage
// Create session (expires in 24 hours)
await env.kv.setJson(`session:${sessionId}`, {
userId: user.id,
email: user.email,
createdAt: Date.now(),
}, { ttl: 86400 });
// Validate session
const session = await env.kv.getJson(`session:${sessionId}`);
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
Feature flags
const flags = await env.kv.getJson("feature-flags") ?? {};
if (flags.newCheckout) {
// Render new checkout flow
}
Rate limiting
const key = `ratelimit:${clientIp}:${minute}`;
const count = await env.kv.get(key);
const current = count ? parseInt(count) : 0;
if (current >= 100) {
return new Response("Too Many Requests", { status: 429 });
}
await env.kv.set(key, String(current + 1), { ttl: 60 });
Optimistic locking
// Read with generation number
const { value, generation } = await env.kv.getWithMetadata("counter");
const count = value ? parseInt(value) : 0;
// Write with generation check — fails if another writer changed the value
await env.kv.set("counter", String(count + 1), { generation });
Key Naming Conventions
user:{id} -> User profile data
session:{token} -> Session data
cache:{url-hash} -> Cached responses
ratelimit:{ip}:{min} -> Rate limit counters
flag:{name} -> Feature flags
Limitations
- Key size — maximum 1,024 bytes
- Value size — maximum 25 MB per value
- Eventual consistency — writes visible globally within a few seconds
- No atomic increment — use read-modify-write with optimistic locking via generation numbers
- List prefix restrictions — prefixes cannot contain
! " $ % & ( ) * + , / \ : < = > @ [ ] { }
Further Reading
- Virke Run — Build compute functions that use Virke KV
- CLI Commands — Full
virke kvcommand reference