Skip to content

Cloudflare D1

This guide provides specific information about using workers-qb with Cloudflare D1, Cloudflare's serverless SQL database.

Cloudflare D1 Database

Cloudflare D1 is a globally distributed SQL database designed for serverless environments. It's directly integrated with Cloudflare Workers, providing low-latency access to data from the edge. workers-qb is particularly well-suited for working with D1 due to its lightweight nature and focus on performance in edge contexts.

D1QB Class

To interact with Cloudflare D1 databases using workers-qb, you will use the D1QB class. This class extends the base QueryBuilder and is specifically tailored for D1's API.

Connecting to D1

To connect to your D1 database, you need to obtain a D1 database binding from your Cloudflare Worker environment. This binding is then passed to the D1QB constructor.

Example: Connecting to D1 in a Cloudflare Worker

typescript
import { D1QB } from 'workers-qb';

// Define your database schema for type-safe queries
type Schema = {
  users: {
    id: number;
    name: string;
    email: string;
    created_at: string;
  };
  products: {
    id: number;
    name: string;
    price: number;
  };
};

export interface Env {
  DB: D1Database; // 'DB' is the name of your D1 database binding in wrangler.toml
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Initialize D1QB with schema for type-safe queries
    const qb = new D1QB<Schema>(env.DB);

    // ... your queries using qb ...

    return new Response("D1 queries executed");
  },
};

Make sure you have configured your D1 database binding correctly in your wrangler.toml file. For example:

toml
# wrangler.toml
name = "my-worker-app"
main = "src/index.ts"
compatibility_date = "2023-08-01"

[[d1_databases]]
binding = "DB" # This 'DB' binding name should match the 'Env' interface
database_name = "my-database"
database_id = "your-database-uuid"

D1 Specific Examples

Here are examples demonstrating common operations using D1QB.

Basic Queries with D1QB

All basic and advanced query operations described in the Basic Queries and Advanced Queries sections are fully applicable to D1QB. You can use createTable, dropTable, insert, select, update, delete, joins, modular select builder, where clauses, etc., just as demonstrated in those sections, replacing PGQB or DOQB with D1QB.

Example: Inserting and Fetching Data in D1

typescript
import { D1QB } from 'workers-qb';

// Define your database schema
type Schema = {
  products: {
    id: number;
    name: string;
    price: number;
  };
};

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB<Schema>(env.DB);

    // Create table (if not exists) - you'd typically do this in migrations
    await qb.createTable({
        tableName: 'products',
        ifNotExists: true,
        schema: `
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            price REAL NOT NULL
        `
    }).execute();

    // Insert a product - table name and columns are autocompleted
    const insertedProduct = await qb.insert({
      tableName: 'products',  // ✓ Autocomplete: 'products'
      data: {
        name: 'Example Product',  // ✓ Only valid columns allowed
        price: 19.99,
      },
      returning: ['id', 'name', 'price'],  // ✓ Autocomplete for columns
    }).execute();

    console.log('Inserted product:', insertedProduct.results);

    // Fetch all products - result type is automatically inferred
    const allProducts = await qb.fetchAll({
      tableName: 'products',
    }).execute();

    // allProducts.results is typed as Schema['products'][]
    console.log('All products:', allProducts.results);

    return Response.json({
      insertedProduct: insertedProduct.results,
      allProducts: allProducts.results,
    });
  },
};

Batch Operations in D1

D1 supports batch operations, which can significantly improve performance when executing multiple queries at once. workers-qb provides the batchExecute method on D1QB to leverage this feature.

Example: Batch Inserting Multiple Products

typescript
import { D1QB } from 'workers-qb';

// Define your database schema
type Schema = {
  products: {
    id: number;
    name: string;
    price: number;
  };
};

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB<Schema>(env.DB);

    const productsToInsert = [
      { name: 'Product A', price: 9.99 },
      { name: 'Product B', price: 24.50 },
      { name: 'Product C', price: 15.75 },
    ];

    // Create an array of Query objects for batch execution
    const insertQueries = productsToInsert.map(productData =>
      qb.insert({
        tableName: 'products',
        data: productData,
        returning: ['id', 'name', 'price'],
      }) // insert() returns a Query object directly
    );

    const batchResults = await qb.batchExecute(insertQueries);

    const insertedProducts = batchResults.map(result => result.results);

    console.log('Batch inserted products:', insertedProducts);

    return Response.json({
      batchInsertedProducts: insertedProducts,
    });
  },
};

In this example, we create an array of Query objects from individual insert operations and then execute them all in a single batch using qb.batchExecute(). The insert(), update(), and delete() methods return Query objects directly, which can be passed to batchExecute(). This is more efficient than executing each query separately.

Note: Batch operations in D1 have limitations. Refer to the Cloudflare D1 documentation for details on batch operation constraints.

Execution Metrics

When you execute a query with D1QB, the returned result object contains metrics about the database operation. This includes rowsRead and rowsWritten, which provide insight into the impact of your query.

  • rowsRead: The number of rows read from the database to execute the query.
  • rowsWritten: The number of rows written (inserted, updated, or deleted) to the database.

These metrics can be useful for monitoring and optimizing your database queries.

Example: Accessing Execution Metrics

typescript
import { D1QB } from 'workers-qb';

// Define your database schema
type Schema = {
  users: {
    id: number;
    name: string;
  };
};

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB<Schema>(env.DB);

    // Example of an insert operation
    const insertResult = await qb.insert({
      tableName: 'users',
      data: { name: 'John Doe' },
      returning: ['id', 'name'],
    }).execute();

    console.log(`Rows written: ${insertResult.rowsWritten}`); // e.g., "Rows written: 1"

    // Example of a select operation
    const selectResult = await qb.fetchAll({
      tableName: 'users',
    }).execute();

    console.log(`Rows read: ${selectResult.rowsRead}`); // e.g., "Rows read: 5"

    return Response.json({
      insertedUser: insertResult.results,
      allUsers: selectResult.results,
      metrics: {
        insert: {
          rowsWritten: insertResult.rowsWritten,
          rowsRead: insertResult.rowsRead,
        },
        select: {
          rowsWritten: selectResult.rowsWritten,
          rowsRead: selectResult.rowsRead,
        },
      },
    });
  },
};

Released under the MIT License.