Quickback Docs

Using D1

Drizzle queries, dual-DB mode, and schema patterns with Cloudflare D1

This page covers how to use Cloudflare D1 with Drizzle ORM in your Quickback Stack application.

Drizzle Queries

The generated API uses Drizzle ORM for all database operations:

import { db } from '../db';
import { rooms } from '../schema';
import { eq } from 'drizzle-orm';

// Select
const allRooms = await db.select().from(rooms);

// Where clause
const room = await db.select().from(rooms).where(eq(rooms.id, id));

// Insert
await db.insert(rooms).values({ name: 'Conference A' });

Dual-DB Mode

Quickback supports separate databases for auth and application data, useful for multi-tenant SaaS apps.

Migrations

Migrations are generated by the compiler using drizzle-kit generate. Apply them with:

npx wrangler d1 migrations apply my-db

Backend stalls

D1 statements occasionally wait tens of seconds in Cloudflare's backend queue (a noisy neighbour on the shared execution queue — not your workload), and the D1 binding has no deadline or AbortSignal of its own. Because every query runs through generated code, the deadline is a config option rather than something you wrap by hand:

database: defineDatabase("cloudflare-d1", {
  queryTimeoutMs: 5000,
}),

With it set, every D1 handle the compiler emits — the scoped db, the unsafe db, the Better Auth adapter, cron / queue / email runners, the files and audit databases — races each statement against the deadline:

StatementResponseClient should
Read (SELECT, WITH, EXPLAIN, PRAGMA)503 DB_TIMEOUTRetry. The read had no side effects
Write (anything else, and any batch containing a write)500 DB_WRITE_OUTCOME_UNKNOWNRe-read the record, then decide. The statement was not cancelled on the backend and may still commit

Each timeout also logs one structured line — { level: "error", event: "DB_DEADLINE", binding, kind, ms, sql } (SQL truncated to 200 chars). Workers Logs indexes the JSON fields, so in the dashboard or wrangler tail --format json filter on event = "DB_DEADLINE" and group by binding / kind to track stalls over time. The response the client saw carries the same binding in details. Unset, nothing changes: no wrapper is emitted and queries are unbounded, as before. The option is D1-only; Postgres drivers carry their own statement timeouts. With readReplication on, the deadline wraps the request's D1 session too.

  • D1 Setup — Bindings and wrangler config
  • Read replication — Serve reads from regional replicas with the Sessions API
  • Schema — Defining your database schema

On this page