The Cloudflare Data Layer Trio: D1, KV, and Queues in Practice

The Cloudflare Data Layer Trio: D1, KV, and Queues in Practice

Completing the Cloudflare series: I previously wrote about building a free blog on Workers and R2 object storage. This post covers the data layer — D1, KV, and Queues, the three fundamentals for storing data in Worker apps: relational data, key-value data, and async tasks.

Know the Three First

ServiceTypeBest ForConsistency
D1Relational DB (SQLite-compatible)Structured data, SQL queries, user dataStrong
KVGlobal key-value storeConfig, cache, sessions, read-heavyEventually consistent
QueuesMessage queueAsync tasks, load shedding, decouplingAt-least-once delivery

In one sentence: need SQL? D1. Need lightning-fast config reads? KV. Need to queue work? Queues.

D1: The Relational Database

D1 is Cloudflare’s globally distributed database built on SQLite — you write plain SQL right inside a Worker.

Create and Bind

wrangler d1 create my-blog-db

Bind it in wrangler.toml:

[[d1_databases]]
binding = "DB"          # variable name inside the Worker
database_name = "my-blog-db"
database_id = "<id generated at creation>"

Schema and Queries

Create a local schema (schema.sql):

CREATE TABLE IF NOT EXISTS comments (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  post_id TEXT NOT NULL,
  content TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now'))
);

Query it from a Worker:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const postId = url.searchParams.get("post");

    const { results } = await env.DB.prepare(
      "SELECT * FROM comments WHERE post_id = ? ORDER BY created_at DESC"
    ).bind(postId).all();

    return Response.json(results);
  },
};

prepare().bind().all() is the standard D1 three-step; parameterized and injection-safe, just like any other SQLite usage.

KV: The Key-Value Store

KV is a globally replicated key-value store with millisecond edge reads — perfect for config, cache, and other read-heavy, write-light data.

wrangler kv namespace create BLOG_KV

Bind:

[[kv_namespaces]]
binding = "BLOG_KV"
id = "<id generated at creation>"

Read and write from a Worker:

// Read
const cached = await env.BLOG_KV.get("homepage:latest");
if (cached) return new Response(cached);

// Write (optional TTL in seconds)
await env.BLOG_KV.put("homepage:latest", html, { expirationTtl: 600 });

The classic KV pattern: check cache first, compute on miss, write back.

Queues: The Message Queue

Queues lets Workers pass messages to each other asynchronously — great for pulling slow work out of the request path.

[[queues.producers]]
binding = "MY_QUEUE"   # producer binding
queue = "my-queue"

[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10

Produce a message:

// Generate a thumbnail after an image upload
await env.MY_QUEUE.send({ task: "resize", key: "photo-1.webp" });
return new Response("queued");

Consume it (in the same Worker file):

export default {
  async queue(batch, env) {
    for (const msg of batch.messages) {
      const { task, key } = msg.body;
      // do the slow work here: thumbnails, emails, sync...
      console.log(`processing ${task}: ${key}`);
    }
  },
};

The request returns “queued” immediately while the real work happens in the queue — that’s load shedding.

A Typical Combination

A full-stack Cloudflare blog/app can be assembled like this:

  • D1: posts, comments, user data
  • KV: homepage HTML cache, site config, session tokens
  • R2: images and static files (see the R2 guide)
  • Queues: thumbnail generation and notifications after R2 uploads

Pricing

All three come with generous free tiers (D1: storage and reads/writes; KV: operations; Queues: messages) — more than enough for personal projects. Beyond that, usage-based pricing applies; see the Cloudflare pricing page for current numbers.

Summary

D1, KV, and Queues complete the “store” side of Worker apps: relational, key-value, and queueing, each covering its own lane — free-tier start, serverless, and globally distributed. Combined with the existing Workers + R2 + Tunnel guides, a full-stack app can run on Cloudflare at zero cost.