Cloudflare D1 is a serverless, distributed SQL database built on SQLite, designed to integrate seamlessly with Cloudflare Workers for building full-stack applications on Cloudflare’s global network. It provides a lightweight, low-latency, and cost-effective solution for relational data storage, with features like automatic read replication, point-in-time recovery, and compatibility with popular ORMs like Prisma. Below, I’ll cover the key knowledge points of D1, tailored to your tech stack (Next.js, TypeScript, Cloudflare), leveraging the provided web and X post references and the official documentation.


What is Cloudflare D1?

D1 is Cloudflare’s native serverless SQL database, launched in alpha in 2022 and generally available (GA) as of April 2024. It’s built on SQLite, one of the most widely used database engines, and is designed to provide relational data storage with SQL semantics for Cloudflare Workers applications. D1 enables developers to build scalable, full-stack applications without managing infrastructure, focusing on simplicity, performance, and global distribution.

Key characteristics include:

  • Serverless Architecture: Scales to zero, meaning you’re only billed for queries and storage, not idle time.
  • SQLite-Based: Uses SQLite’s SQL dialect, supporting familiar queries, foreign keys, and JSON parsing.
  • Global Distribution: Leverages Cloudflare’s network for low-latency access, with automatic read replication to route queries to nearby replicas.
  • Integration with Workers: Natively integrates with Cloudflare Workers via bindings, enabling programmatic database access.
  • Cost-Effective Pricing: No egress charges, with pricing based on rows read/written and storage above free tier limits.

Key Knowledge Points

1. Core Features

  • Relational SQL Database:
    • D1 supports SQLite’s SQL syntax, including SELECT, INSERT, UPDATE, DELETE, and Data Definition Language (DDL) operations like CREATE, ALTER, and DROP. It also supports foreign key constraints and JSON querying.
    • Example:
      SELECT * FROM users WHERE created_at > ?1;
      

      This query can leverage indexes to reduce rows scanned, improving performance and cost.

  • Time Travel (Point-in-Time Recovery):
    • D1’s Time Travel feature allows restoring a database to any minute within the last 30 days using the Write-Ahead Log (WAL). This is built-in for databases on D1’s new storage system and requires no manual backups.
    • Example using Wrangler:
      wrangler d1 time-travel my-database --before-timestamp=1683570504
      

      or

      wrangler d1 time-travel my-database --before-tx-id=01H0FM2XHKACETEFQK2P5T6BWD
      
  • Global Read Replication (Beta):
    • Introduced in Developer Week 2025, D1’s read replication automatically provisions read-only replicas in multiple regions to reduce latency and increase throughput. Replicas are managed by Cloudflare based on query volume and location, requiring no configuration.
    • Sequential Consistency: D1 uses a session-based model (withSession) to ensure sequential consistency across queries in a session, even when switching replicas. Options include first-primary (first query hits the primary database) or first-unconstrained (first query can hit any replica).
    • Bookmark System: Sessions can pass a bookmark to maintain consistency across requests, ensuring subsequent queries see at least the same database state.
    • Example:
      const session = env.DB.withSession("first-primary");
      const result = await session.prepare("SELECT * FROM users").all();
      const bookmark = await session.getBookmark();
      
  • D1 Insights:
    • Enhanced query debugging provides metrics like rows read/written and query duration via the Cloudflare dashboard, meta object, or GraphQL Analytics API.
    • Example:
      const result = await env.DB.prepare("SELECT * FROM users").all();
      console.log(result.meta); // { rows_read: 5000, rows_written: 0, duration: 10 }
      
  • Data Export:
    • D1 supports exporting the entire database to an SQLite-compatible file in an ArrayBuffer, useful for backups or migrations. This API is limited to alpha-period databases.
    • Example:
      const dump = await env.DB.dump();
      

2. Integration with Cloudflare Workers

D1 integrates with Cloudflare Workers via bindings, allowing programmatic access from TypeScript/JavaScript code. Workers can use the D1Database API for querying, batch operations, and sessions.

  • Bindings:
    • Bind a D1 database to a Worker in wrangler.toml:
      [[d1_databases]]
      binding = "DB"
      database_name = "prod-d1-tutorial"
      database_id = "<D1_DATABASE_ID>"
      

      This makes the database available as env.DB in your Worker.

  • Querying:
    • Use prepare for parameterized queries to prevent SQL injection, exec for direct queries, or batch for multiple statements to reduce latency.
    • Example:
      export interface Env {
        DB: D1Database;
      }
      export default {
        async fetch(request: Request, env: Env): Promise<Response> {
          const result = await env.DB.prepare("SELECT * FROM users WHERE id = ?1").bind(1).all();
          return Response.json(result.results);
        },
      };
      
  • Batch Operations:
    • Batch multiple statements to reduce network round-trips:
      const statements = [
        env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Alice"),
        env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
      ];
      const results = await env.DB.batch(statements);
      
  • Limitations:
    • Batch calls must resolve within 30 seconds.
    • Transactions are not supported, so batch operations run sequentially with auto-commit, breaking ACID transaction guarantees.

3. Prisma ORM Integration

D1 supports Prisma ORM in Preview mode, enabling type-safe queries and schema management for Next.js and TypeScript applications.

  • Setup:
    • Use the @prisma/adapter-d1 driver adapter and @prisma/client/edge for Cloudflare compatibility:
      npm install prisma @prisma/client @prisma/adapter-d1
      npx prisma init
      
    • Configure schema.prisma:
      generator client {
        provider = "prisma-client-js"
        previewFeatures = ["driverAdapters"]
      }
      datasource db {
        provider = "sqlite"
        url      = env("DATABASE_URL")
      }
      model User {
        id    String  @id @default(uuid())
        name  String?
      }
      
  • Schema Migrations:
    • D1 doesn’t fully support prisma migrate dev. Instead, use prisma migrate diff to generate SQL migrations and apply them with wrangler d1 migrations apply:
      npx prisma migrate diff --from-empty --to-schema-datamodel ./prisma/schema.prisma --script --output migrations/0001_create_user_table.sql
      npx wrangler d1 migrations apply prod-d1-tutorial --local
      
    • Alternatively, use prisma db push for schema syncing without migration files (v6.6.0+).
  • Querying with Prisma:
    • Initialize Prisma Client with the D1 adapter:
      import { PrismaClient } from '@prisma/client/edge';
      import { PrismaD1 } from '@prisma/adapter-d1';
      export interface Env {
        DB: D1Database;
      }
      const prismaClientSingleton = () => {
        const adapter = new PrismaD1(env.DB);
        return new PrismaClient({ adapter });
      };
      const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
      export const prisma = globalForPrisma.prisma ?? prismaClientSingleton();
      if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
      
    • Example query in a Next.js Server Component:
      import { prisma } from '@/lib/prisma';
      export default async function Home() {
        const users = await prisma.user.findMany();
        return (
          <div>
            {users.map((user) => (
              <div key={user.id}>{user.name}</div>
            ))}
          </div>
        );
      }
      
  • Limitations:
    • Transactions are not supported, so Prisma runs queries individually, breaking ACID guarantees.
    • Use prisma db push or manual migrations for schema changes, as migrate dev is not fully supported.

4. Deployment and Management

  • Creating a Database:
    • Via Cloudflare dashboard: Navigate to Storage & Databases > D1 SQL Database > Create Database.
    • Via Wrangler CLI:
      npx wrangler d1 create prod-d1-tutorial
      

      Add the binding to wrangler.toml.

  • Location Hints:
    • Optionally specify a geographical location hint to optimize database placement (e.g., closer to users).
  • Wrangler CLI:
    • Manage D1 databases with commands like:
      npx wrangler d1 execute prod-d1-tutorial --command="CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
      npx wrangler d1 migrations apply prod-d1-tutorial
      
  • Dashboard:
    • The Cloudflare dashboard provides a UI for creating tables, querying data, and triggering Time Travel restores. Changes are instantly available to Workers without redeployment.
  • Local Development:
    • Use wrangler d1 --local to manage a local SQLite instance in .wrangler/state. Remote instances are accessed via HTTP.

5. Pricing and Limits

  • Pricing:
    • Free Tier: Includes 5 GB storage, 25 million rows read/day, 100,000 rows written/day, and 10,000 databases per account.
    • Paid Plan: Additional reads ($0.001/100,000 rows), writes ($0.001/1,000 rows), and storage ($0.001/GB/month) above free tier limits. No egress or compute charges for idle time.
    • Example: A query scanning 10,000 rows (64 bytes each) consumes 160 read units; inserting a 3 KB row consumes 3 write units.
  • Limits:
    • Storage: 10 GB per database, 50,000 databases per account (Paid plan). Free plan limited to 5 GB total.
    • Connections: Up to 6 simultaneous connections per Worker invocation.
    • Bindings: A Worker can bind up to ~5,000 D1 databases (1 MB script metadata limit).
    • Query Duration: Batch calls must resolve in 30 seconds.
    • Rows: No limit on row size, but queries scanning large datasets (e.g., 40 million rows) can incur high costs if unoptimized.
  • Cost Optimization:
    • Create indexes on frequently queried columns to reduce rows scanned.
    • Use batch operations to minimize network round-trips.

6. Integration with Next.js and TypeScript

D1 is well-suited for Next.js applications, especially with the App Router and TypeScript, due to its Prisma integration and edge runtime compatibility.

  • Server Components:
    • Query D1 directly in Server Components for static or server-side rendering:
      import { prisma } from '@/lib/prisma';
      export default async function Home() {
        const users = await prisma.user.findMany();
        return (
          <div>
            {users.map((user) => (
              <div key={user.id}>{user.name}</div>
            ))}
          </div>
        );
      }
      
  • Server Actions:
    • Use Server Actions for mutations:
      'use server';
      import { prisma } from '@/lib/prisma';
      export async function createUser(formData: FormData) {
        const name = formData.get('name') as string;
        await prisma.user.create({ data: { name } });
      }
      
  • Edge Runtime:
    • Use @prisma/client/edge and @prisma/adapter-d1 for Cloudflare Pages or Workers. Configure wrangler.toml for D1 bindings:
      compatibility_flags = ["nodejs_compat"]
      [[d1_databases]]
      binding = "DB"
      database_name = "prod-d1-tutorial"
      database_id = "<D1_DATABASE_ID>"
      
  • Deployment:
    • For Cloudflare Pages, use @cloudflare/next-on-pages and run prisma generate --no-engine during build.
    • For Workers, use @opennextjs/cloudflare for full-stack Next.js apps. Set DATABASE_URL in .dev.vars or the Cloudflare dashboard.

7. Other ORMs and Tools

Besides Prisma, D1 supports community-built tools:

  • Drizzle ORM: TypeScript ORM with automatic schema generation and edge compatibility.
  • Kysely: Type-safe SQL query builder with a D1 adapter.
  • Sutando: Node.js ORM for CRUD operations.
  • workers-qb: Lightweight query builder for direct SQL access.
  • d1-console: CLI tool for interactive database management.
  • NuxtHub: Nuxt module for D1 integration in Nuxt applications.

8. Use Cases

D1 is ideal for applications requiring relational data with low latency and global access:

  • E-commerce Sites: Store product and order data, leveraging read replication for fast reads.
  • SaaS Applications: Use per-tenant databases for data isolation.
  • Internal Dashboards: Combine with Cloudflare Access for secure admin tools.
  • Real-Time Apps: Pair with Durable Objects for stateful coordination (e.g., chat or dashboards).
  • AI Applications: Store metadata for AI models, as used by Workers AI.

Example demos include:

  • Staff Directory: Built with HonoX, Cloudflare Pages, and D1.
  • Wildebeest: ActivityPub/Mastodon-compatible server.
  • Northwind Demo: Showcases D1 with a sample dataset.

9. D1 vs. Durable Objects

D1 and Durable Objects serve different purposes:

  • D1: Relational SQL database for structured data, supporting complex queries and joins. Ideal for traditional database use cases like e-commerce or CRMs.
  • Durable Objects: Single-location, strongly consistent storage with co-located compute, suited for real-time coordination (e.g., chat, multiplayer games). Each object has private SQLite storage (up to 10 GB).
  • Use Case Example:
    • Use D1 for a Next.js app’s user and order data with Prisma.
    • Use Durable Objects for a real-time chat feature, storing message history in its SQLite backend.

10. Best Practices

  • Indexing: Create indexes on frequently queried columns to reduce rows scanned and costs.
  • Batch Queries: Use batch or Prisma’s batch operations to minimize latency.
  • Smaller Databases: Split monolithic databases into per-user/tenant databases for better isolation and performance.
  • Monitor Usage: Use D1 Insights (meta object or dashboard) to track rows read/written and optimize queries.
  • Edge Compatibility: Use @prisma/client/edge and driver adapters for Cloudflare Pages/Workers.
  • Backup Strategy: Leverage Time Travel for automatic backups; export data periodically for external storage.

11. Challenges and Workarounds

  • No Transactions: D1 lacks transaction support, so use batch for sequential operations or rely on Durable Objects for transactional logic.
  • Migration Limitations: Manual migration application (wrangler d1 migrations apply) is required for D1. Use prisma migrate diff for schema changes.
  • Performance Concerns: Unoptimized queries (e.g., full table scans) can lead to high costs. Always use indexes and monitor with D1 Insights.
  • Edge Runtime Issues: Ensure nodejs_compat flag is enabled in wrangler.toml and environment variables are set correctly.

Example Setup for Next.js, TypeScript, and D1

  1. Initialize Project:
    npx create-next-app@latest --typescript
    npm install prisma @prisma/client @prisma/adapter-d1
    npx prisma init
    
  2. Configure D1:
    • Create a D1 database:
      npx wrangler d1 create prod-d1-tutorial
      
    • Update wrangler.toml:
      compatibility_flags = ["nodejs_compat"]
      [[d1_databases]]
      binding = "DB"
      database_name = "prod-d1-tutorial"
      database_id = "<D1_DATABASE_ID>"
      
  3. Prisma Schema:
    generator client {
      provider = "prisma-client-js"
      previewFeatures = ["driverAdapters"]
    }
    datasource db {
      provider = "sqlite"
      url      = env("DATABASE_URL")
    }
    model User {
      id    String  @id @default(uuid())
      name  String?
    }
    
  4. Prisma Client:
    // lib/prisma.ts
    import { PrismaClient } from '@prisma/client/edge';
    import { PrismaD1 } from '@prisma/adapter-d1';
    export interface Env {
      DB: D1Database;
    }
    const prismaClientSingleton = (env: Env) => {
      const adapter = new PrismaD1(env.DB);
      return new PrismaClient({ adapter });
    };
    const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
    export const prisma = globalForPrisma.prisma ?? prismaClientSingleton(env);
    if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
    
  5. Query in Next.js:
    // app/api/users/route.ts
    import { prisma } from '@/lib/prisma';
    export async function GET() {
      const users = await prisma.user.findMany();
      return Response.json(users);
    }
    
  6. Deploy:
    • For Pages:
      npx @cloudflare/next-on-pages
      

      Set DATABASE_URL in the Cloudflare dashboard.

    • For Workers:
      npx opennextjs-cloudflare build
      npx opennextjs-cloudflare deploy
      

Conclusion

Cloudflare D1 is a powerful serverless SQL database for Next.js and TypeScript applications, offering low-latency relational storage, global read replication, and seamless integration with Cloudflare Workers. Its SQLite foundation, Time Travel backups, and Prisma support make it ideal for full-stack apps, while its pricing and scalability suit both prototyping and production. By combining D1 with Durable Objects, you can build stateful, real-time applications entirely on Cloudflare’s edge. For detailed guides, explore the Cloudflare D1 documentation and community resources like the Developer Discord.

Comments