Claude Code for Deno KV: Built-In Key-Value Store and Queue — Claude Skills 360 Blog
Blog / Backend / Claude Code for Deno KV: Built-In Key-Value Store and Queue
Backend

Claude Code for Deno KV: Built-In Key-Value Store and Queue

Published: January 30, 2027
Read time: 8 min read
By: Claude Skills 360

Deno KV is a globally replicated key-value database built directly into the Deno runtime — no external service or driver needed. await Deno.openKv() opens a database in development, automatically backed by FoundationDB on Deno Deploy for production global replication. Keys are tuples of strings and numbers. Atomic transactions check-and-set multiple keys consistently. kv.list() scans key ranges with prefix filters. kv.enqueue() pushes durable messages consumed by kv.listenQueue(). kv.watch() streams value updates to connected clients for real-time data. Deno Deploy runs Deno KV at the edge across 35 global regions — reads are strongly consistent within a region. Claude Code generates Deno KV data models, atomic transaction patterns, queue workers, watch-based subscriptions, and secondary index patterns for production Deno applications.

CLAUDE.md for Deno KV Projects

## Deno KV Stack
- Runtime: Deno >= 1.45 — Deno KV is stable
- Open: const kv = await Deno.openKv() — local SQLite in dev, FoundationDB on Deploy
- Keys: Deno.KvKey — tuple of strings/numbers: ["orders", userId, orderId]
- Atomic: kv.atomic().check(...).set(...).delete(...).commit() — all-or-nothing
- List: kv.list({ prefix: ["orders", userId] }) — returns AsyncIterator
- Queue: kv.enqueue(payload, { delay: 0 }) + kv.listenQueue(handler)
- Watch: kv.watch([key]) — AsyncIterator of value change events
- Test: Deno.openKv(":memory:") — in-memory for unit tests

Data Models and CRUD

// lib/orders.ts — Deno KV data access layer
const kv = await Deno.openKv()

interface Order {
  id: string
  customerId: string
  status: "pending" | "processing" | "shipped" | "delivered" | "cancelled"
  totalCents: number
  items: OrderItem[]
  createdAt: string
  updatedAt: string
}

interface OrderItem {
  productId: string
  name: string
  quantity: number
  priceCents: number
}

// Key structure:
// ["orders", orderId]            — primary key
// ["orders_by_customer", customerId, createdAt, orderId]  — secondary index
// ["orders_by_status", status, orderId]                   — secondary index

export async function createOrder(
  customerId: string,
  items: OrderItem[]
): Promise<Order> {
  const id = crypto.randomUUID()
  const now = new Date().toISOString()
  const totalCents = items.reduce(
    (sum, item) => sum + item.priceCents * item.quantity,
    0
  )

  const order: Order = {
    id,
    customerId,
    status: "pending",
    totalCents,
    items,
    createdAt: now,
    updatedAt: now,
  }

  // Atomic: write primary + both secondary indexes together
  const result = await kv.atomic()
    .set(["orders", id], order)
    .set(["orders_by_customer", customerId, now, id], { id, totalCents, status: "pending" })
    .set(["orders_by_status", "pending", id], { id, customerId, totalCents })
    .commit()

  if (!result.ok) throw new Error("Failed to create order — transaction conflict")

  return order
}

export async function getOrder(orderId: string): Promise<Order | null> {
  const entry = await kv.get<Order>(["orders", orderId])
  return entry.value
}

export async function updateOrderStatus(
  orderId: string,
  newStatus: Order["status"]
): Promise<Order> {
  // Read-modify-write with optimistic concurrency
  while (true) {
    const entry = await kv.get<Order>(["orders", orderId])
    if (!entry.value) throw new Error(`Order ${orderId} not found`)

    const oldStatus = entry.value.status
    const updated: Order = {
      ...entry.value,
      status: newStatus,
      updatedAt: new Date().toISOString(),
    }

    const result = await kv.atomic()
      // Check version hasn't changed since we read
      .check(entry)
      .set(["orders", orderId], updated)
      // Update secondary indexes
      .delete(["orders_by_status", oldStatus, orderId])
      .set(["orders_by_status", newStatus, orderId], {
        id: orderId,
        customerId: updated.customerId,
        totalCents: updated.totalCents,
      })
      .commit()

    if (result.ok) return updated
    // Retry on conflict (another write happened between read and write)
  }
}

export async function listCustomerOrders(
  customerId: string,
  limit = 20
): Promise<Order[]> {
  const orders: Order[] = []

  // Scan secondary index prefix
  const entries = kv.list<{ id: string }>(
    { prefix: ["orders_by_customer", customerId] },
    { limit, reverse: true }  // Newest first
  )

  for await (const entry of entries) {
    const order = await getOrder(entry.value.id)
    if (order) orders.push(order)
  }

  return orders
}

KV Queue

// lib/queue.ts — Deno KV queue for background work
interface FulfillmentJob {
  type: "fulfillment"
  orderId: string
  customerId: string
}

interface EmailJob {
  type: "email"
  to: string
  template: string
  vars: Record<string, unknown>
}

type QueueJob = FulfillmentJob | EmailJob

export async function enqueueOrderFulfillment(
  orderId: string,
  customerId: string
): Promise<void> {
  await kv.enqueue(
    { type: "fulfillment", orderId, customerId } satisfies FulfillmentJob,
    { delay: 0 }
  )
}

export async function enqueueEmail(
  to: string,
  template: string,
  vars: Record<string, unknown>
): Promise<void> {
  await kv.enqueue(
    { type: "email", to, template, vars } satisfies EmailJob,
    { delay: 0 }
  )
}

// Worker: call once at startup — processes messages indefinitely
export function startQueueWorker(): void {
  kv.listenQueue(async (job: QueueJob) => {
    console.log("Processing job", job.type)

    if (job.type === "fulfillment") {
      await processFulfillment(job.orderId, job.customerId)
    } else if (job.type === "email") {
      await sendEmail(job.to, job.template, job.vars)
    }
  })
  console.log("Queue worker started")
}

async function processFulfillment(
  orderId: string,
  customerId: string
): Promise<void> {
  await updateOrderStatus(orderId, "processing")

  const result = await fetch("https://fulfillment.api.com/orders", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ orderId, customerId }),
  })

  if (!result.ok) throw new Error(`Fulfillment API error: ${result.status}`)

  const { fulfillmentId } = await result.json()
  await updateOrderStatus(orderId, "fulfilled")
  await enqueueEmail(customerId, "order-fulfilled", { orderId, fulfillmentId })
}

Watch for Real-Time Updates

// routes/orders/[id]/stream.ts — Server-Sent Events with kv.watch
import { Hono } from "hono"

const app = new Hono()

app.get("/orders/:id/stream", async (c) => {
  const orderId = c.req.param("id")

  // Verify order exists
  const order = await getOrder(orderId)
  if (!order) return c.json({ error: "Not found" }, 404)

  // Stream SSE to client
  const stream = new ReadableStream({
    async start(controller) {
      const watcher = kv.watch<Order>([["orders", orderId]])

      for await (const [entry] of watcher) {
        if (entry.value === null) {
          controller.close()
          break
        }

        const data = JSON.stringify({
          id: entry.value.id,
          status: entry.value.status,
          updatedAt: entry.value.updatedAt,
        })

        controller.enqueue(`data: ${data}\n\n`)

        // Close stream when order reaches terminal state
        if (["delivered", "cancelled"].includes(entry.value.status)) {
          controller.enqueue(`event: done\ndata: {}\n\n`)
          controller.close()
          break
        }
      }
    },
  })

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
    },
  })
})

Testing with In-Memory KV

// tests/orders.test.ts — deterministic unit tests with in-memory KV
import { assertEquals, assertRejects } from "@std/assert"

// Override with in-memory database for tests
Deno.test("createOrder stores order with correct structure", async () => {
  // Swap global kv for in-memory instance
  const testKv = await Deno.openKv(":memory:")

  const items = [
    { productId: "prod-1", name: "Widget", quantity: 2, priceCents: 999 },
  ]

  const order = await createOrderWithKv(testKv, "cust-123", items)

  assertEquals(order.customerId, "cust-123")
  assertEquals(order.status, "pending")
  assertEquals(order.totalCents, 1998)
  assertEquals(order.items.length, 1)

  // Verify it's stored
  const stored = await testKv.get<Order>(["orders", order.id])
  assertEquals(stored.value?.id, order.id)

  testKv.close()
})

Deno.test("updateOrderStatus handles concurrent writes correctly", async () => {
  const testKv = await Deno.openKv(":memory:")
  const order = await createOrderWithKv(testKv, "cust-123", [...])

  // Simulate two concurrent updates
  const result = await Promise.all([
    updateOrderStatusWithKv(testKv, order.id, "processing"),
    updateOrderStatusWithKv(testKv, order.id, "cancelled"),
  ])

  // Exactly one should succeed
  const final = await testKv.get<Order>(["orders", order.id])
  const validStatuses = new Set(["processing", "cancelled"])
  assertEquals(validStatuses.has(final.value!.status), true)

  testKv.close()
})

For the Upstash Redis serverless alternative that provides a similar HTTP-based KV API working in edge environments with pub/sub and sorted sets beyond basic KV, see the Redis guide for caching and queue patterns. For Cloudflare Workers KV when deploying on Cloudflare infrastructure with globally distributed eventually-consistent reads at low latency, the Cloudflare Workers guide covers KV namespace bindings. The Claude Skills 360 bundle includes Deno KV skill sets covering atomic transactions, queue workers, and watch subscriptions. Start with the free tier to try Deno KV data model generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free