Skip to content
TopInsight .co
Abstract architectural cross-section showing a Workers compute layer above a D1 SQLite database layer, glowing amber wireframe in dark void.

Cloudflare Workers + D1 in production: the real-world build pattern

D1 hit production-ready in 2024 and matured fast through 2025. After shipping two real SaaS apps on Workers + D1, here is the working stack.

C Charles Lin ·

D1 hit production-ready status in late 2024 and has continued maturing through 2025. After shipping two real SaaS apps on Workers + D1 (one a small B2B tool, one a side-project-turned-business), here is the working stack pattern.

This isn’t a “hello world” tutorial — those exist. This is the architecture pattern that handles real customers, real revenue, and real failure cases.

The stack at a glance

[Frontend: Astro static site or Next.js SPA on CF Pages/Workers Static Assets]


[API Worker — TypeScript, Hono router]

            ├──► D1 (Postgres-like SQLite, transactions, structured data)
            ├──► R2 (object storage — file uploads, user avatars)
            ├──► KV (eventually-consistent caches, session tokens)
            └──► Durable Objects (single-instance state — rate limits, WebSockets)

Everything in one Cloudflare account, one wrangler deploy, one bill.

Why D1 actually works in production now

The honest answer: it didn’t for most of 2024. By mid-2025 the rough edges have smoothed enough that real apps are running on it.

What changed:

  • Latency stabilised. D1 queries from a Worker in the same region: typically 5-30ms. Reasonable.
  • Backups landed. Time Travel restore lets you roll back to any point in the last 30 days. This is the killer feature missing in mid-2024.
  • Read replication shipped (covered in our D1 read replicas analysis). Reads can hit a regional replica; writes go to the primary.
  • D1 size limits expanded. Free tier 5GB, paid tier 10GB per database — workable for most SaaS workloads. Multi-database patterns handle bigger scale.

What still has rough edges:

  • Schema migrations are manual — no first-class migrations framework like Rails / Django. You use Drizzle, Kysely, or raw SQL with wrangler d1 execute.
  • Local dev requires care — Miniflare’s D1 emulation is decent but not identical to production. Test against a real D1 dev database for anything tricky.
  • No JSONB equivalent — SQLite has JSON but the query ergonomics aren’t as good as Postgres.

The wrangler.jsonc that ships our app

{
  "name": "myapp-api",
  "main": "src/index.ts",
  "compatibility_date": "2025-08-01",
  "compatibility_flags": ["nodejs_compat"],
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "myapp-prod",
      "database_id": "...",
      "migrations_dir": "drizzle"
    }
  ],
  "r2_buckets": [
    { "binding": "BUCKET", "bucket_name": "myapp-uploads" }
  ],
  "kv_namespaces": [
    { "binding": "CACHE", "id": "..." }
  ],
  "observability": { "enabled": true }
}

The pattern: every external dependency is a binding. The Worker code accesses everything via env.DB, env.BUCKET, env.CACHE. Local development uses wrangler dev which spins up local emulators for each binding.

The Drizzle migration pattern

For schema management, Drizzle ORM with the D1 adapter is the cleanest 2025 setup:

// schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: text('id').primaryKey(),
  email: text('email').notNull().unique(),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
});
# Generate migration from schema diff
npx drizzle-kit generate

# Apply to local D1
npx wrangler d1 migrations apply myapp-prod --local

# Apply to remote D1
npx wrangler d1 migrations apply myapp-prod

This is the workflow we ship every schema change with. Drizzle’s D1 support has been stable since early 2025.

The transactional pattern

D1 supports BEGIN/COMMIT/ROLLBACK but the API surface is slightly different from Postgres. Use db.batch([...]) for atomic multi-statement work:

const stmts = [
  db.prepare('INSERT INTO orders (id, user_id, total) VALUES (?, ?, ?)').bind(orderId, userId, total),
  db.prepare('UPDATE users SET balance = balance - ? WHERE id = ?').bind(total, userId),
];
await env.DB.batch(stmts);

Either both statements commit or neither does. This handles 90% of the multi-statement transaction needs we’ve had.

For complex multi-step transactions (interactive transactions where you need to read, decide, write based on the read), D1 currently does NOT support that. Use Durable Objects for that pattern — single-instance state with full transactional semantics.

Where D1 doesn’t fit

After two production apps:

  • Heavy analytics queries — D1 is SQLite-based; complex window functions and large aggregations are not its strength. Use ClickHouse Cloud or DuckDB for analytics.
  • >50GB datasets — D1 has limits. Sharding across multiple D1 databases is the workaround, but at that scale Postgres on Neon may be a better fit.
  • Complex JSON-heavy workloads — SQLite JSON works but Postgres JSONB is more ergonomic.
  • Multi-row interactive transactions — see above; use Durable Objects.

What r/CloudFlare is actually saying

The community signal in mid-2025:

The pattern: developers who tried Workers + D1 in 2023–2024 and bounced are coming back in 2025. The platform has matured.

The recommendation

Use Workers + D1 for:

  • SaaS apps with structured data, modest query complexity, sub-50GB total data
  • Apps where edge latency matters
  • Cost-sensitive workloads (the free tier is real)
  • Anything where “one platform, one bill, one deploy” is valuable

Don’t use Workers + D1 for:

  • Heavy analytics (use ClickHouse / DuckDB / BigQuery)
  • Multi-row interactive transactions (use Durable Objects or Postgres)
  • Apps that need a mature migrations framework today (use Rails / Django + Postgres)
  • Massive datasets (use Postgres + read replicas)

For TopInsight, we ship on Workers Static Assets (covered in our Workers Static Assets piece) and plan to add D1 for the upcoming editorial pipeline state.

Sources

Every reference behind this piece. If we make a claim, it's because at least one of these said so — or we lived it ourselves.

  1. Firsthand Shipped two production SaaS apps on Workers + D1 in 2024-2025
  2. Docs Cloudflare D1 documentation — Cloudflare
  3. Blog r/CloudFlare — free tier multiplayer game thread (260 ups) — r/CloudFlare
  4. Blog r/CloudFlare — long time cloud engineer thread (105 ups) — r/CloudFlare
  5. Blog r/CloudFlare — Localflare local dev tool (153 ups) — r/CloudFlare
  6. YouTube Cloudflare developer advocates + community Workers+D1 tutorials — Various