CLOUDFLARE · D1 · SERVERLESS SQL

A real SQLite database running inside a Cloudflare Worker. No servers to provision, no connection pools to manage, no VPC to configure. Just SQL.

Rows returned
Query latency (ms)
Served from
Rows scanned

Pick a query

Loading query...
Click a query on the left to run it.

The Worker code that just ran

// The entire backend for what you're looking at. // No connection string. No pool. No auth. Just a binding. const { results, meta } = await env.DB .prepare("SELECT * FROM Customers WHERE Plan = ?") .bind("Enterprise") .all(); return Response.json({ results, meta });

▶ Why this matters

No infrastructure. You didn't provision a server. You didn't open a firewall rule. You didn't configure a VPC. There's no connection pool to tune, no RDS instance to size, no read replica to route around.

The Worker holds the binding. The env.DB binding is set at deploy time. Your code talks to it like any other object. No credentials in code, no secrets in env vars, no rotation to worry about.

Global by design. D1 replicates reads to more regions the more your app is read. Your write goes to the primary, but reads happen close to your users automatically.

The bill is trivial for small apps. First 5M reads/day and 100k writes/day free. Small SaaS apps live inside the free tier for months.

Already have a database? D1 is for building on Cloudflare from scratch. If you've already got Postgres or MySQL running (RDS, Neon, Supabase, self-hosted), don't migrate it — point Hyperdrive at it and get edge performance without moving the data.

▶ Ways to make D1 faster

Turn on read replication. Wrap your reads in env.DB.withSession(). D1 automatically spins up read replicas in more regions the more the database gets read. A user in Tokyo goes from ~150ms to ~20ms on reads. Biggest single win for global apps.

Batch your queries. Instead of three sequential await calls, use env.DB.batch([stmt1, stmt2, stmt3]). One round trip instead of three. Often 3–10x speedup on pages that do multiple queries.

Add indexes on what you filter. D1 is SQLite — same rules as any SQL database. CREATE INDEX idx_country ON Customers(Country); turns a full table scan into a lookup. Matters more as row count grows.

Cache stable results at the edge. If the data doesn't change often (catalogs, config, leaderboards), wrap the response with Cache-Control: max-age=60. Sub-10ms responses because the query never runs.

Right tool for the job. D1 is transactional SQL. If you're storing millions of event logs and running big time-window aggregations, use Workers Analytics Engine instead. Keep D1 lean.