> ## Documentation Index
> Fetch the complete documentation index at: https://wemstudios.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Querying Cloudflare D1 in Blaze: SQLite at the Edge

> Use Cloudflare D1 (SQLite at the edge) in Blaze via req.env.DB. Prepare parameterized queries, batch statements, and access typed rows in every handler.

Cloudflare D1 is a serverless SQLite database that runs at the edge alongside your Worker. Blaze makes it available on `req.env.DB` in every handler the moment you add the binding to your `Env` type — no connection pooling, no client initialization, just `prepare → bind → execute`.

## Configure the binding

<Steps>
  <Step title="Add the database to wrangler.toml">
    ```toml wrangler.toml theme={null}
    [[d1_databases]]
    binding       = "DB"
    database_name = "myapp"
    database_id   = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    ```
  </Step>

  <Step title="Define the Env type">
    ```typescript src/types/env.ts theme={null}
    export type Env = {
      DB: D1Database;
      // ...other bindings
    };
    ```
  </Step>

  <Step title="Pass Env to createApp">
    ```typescript src/index.ts theme={null}
    import { createApp } from "blaze";
    import type { Env } from "./types/env";

    const app = createApp<Env>();
    // req.env.DB is now fully typed as D1Database in every handler
    ```
  </Step>
</Steps>

## Querying rows

D1 uses a prepared-statement API. Call `.prepare(sql)` to create a statement, `.bind(...values)` to safely interpolate parameters, then either `.first()` for a single row or `.all()` for a full result set.

The example below implements a paginated user listing using `page` and `limit` query parameters:

```typescript theme={null}
type User = {
  id: number;
  name: string;
  email: string;
};

app.get("/users", async (req, res) => {
  const page  = Number(req.query.get("page")  ?? 1);
  const limit = Number(req.query.get("limit") ?? 20);
  const offset = (page - 1) * limit;

  const { results } = await req.env.DB
    .prepare("SELECT id, name, email FROM users LIMIT ? OFFSET ?")
    .bind(limit, offset)
    .all<User>();

  res.json({ page, limit, results });
});
```

## Inserting and updating

Use `.run()` for statements that modify data. When you append `RETURNING id` to an `INSERT`, switch to `.first()` to retrieve the generated ID in a single round-trip.

```typescript theme={null}
app.post("/users", async (req, res) => {
  const { name, email } = await req.json<{ name: string; email: string }>();

  const row = await req.env.DB
    .prepare("INSERT INTO users (name, email) VALUES (?, ?) RETURNING id")
    .bind(name, email)
    .first<{ id: number }>();

  res.status(201).json({ id: row!.id });
});
```

<CodeGroup>
  ```typescript Update a row theme={null}
  app.put("/users/:id", async (req, res) => {
    const { name, email } = await req.json<{ name: string; email: string }>();

    const { success } = await req.env.DB
      .prepare("UPDATE users SET name = ?, email = ? WHERE id = ?")
      .bind(name, email, req.params.id)
      .run();

    if (!success) return res.status(500).json({ error: "Update failed" });

    res.json({ ok: true });
  });
  ```

  ```typescript Delete a row theme={null}
  app.delete("/users/:id", async (req, res) => {
    await req.env.DB
      .prepare("DELETE FROM users WHERE id = ?")
      .bind(req.params.id)
      .run();

    res.status(204).send();
  });
  ```
</CodeGroup>

## Batch statements

`req.env.DB.batch([...])` executes multiple prepared statements in a **single round-trip** and treats them as an atomic unit — if one statement fails, none of the changes are committed. Use batching whenever you need to insert or update multiple related rows together.

```typescript theme={null}
app.post("/users/:id/transfer", async (req, res) => {
  const { toUserId, amount } = await req.json<{
    toUserId: number;
    amount: number;
  }>();

  const [debit, credit] = await req.env.DB.batch([
    req.env.DB
      .prepare("UPDATE accounts SET balance = balance - ? WHERE user_id = ?")
      .bind(amount, req.params.id),
    req.env.DB
      .prepare("UPDATE accounts SET balance = balance + ? WHERE user_id = ?")
      .bind(amount, toUserId),
  ]);

  if (!debit.success || !credit.success) {
    return res.status(500).json({ error: "Transfer failed" });
  }

  res.json({ ok: true, amount });
});
```

## Handling no results

`.first()` returns `null` when no row matches. Always check for `null` before accessing row properties and return a `404` so your client receives a meaningful response.

```typescript theme={null}
app.get("/users/:id", async (req, res) => {
  const user = await req.env.DB
    .prepare("SELECT id, name, email FROM users WHERE id = ?")
    .bind(req.params.id)
    .first<User>();

  if (user === null) {
    return res.status(404).json({ error: "User not found" });
  }

  res.json(user);
});
```

## TypeScript row types

Pass your row shape as a generic argument to `.first<T>()` or `.all<T>()`. D1 returns plain objects, so the generic is a cast — align it with your actual schema to avoid surprises.

```typescript theme={null}
type Post = {
  id: number;
  title: string;
  body: string;
  author_id: number;
  created_at: string;
};

app.get("/posts/:id", async (req, res) => {
  // result is typed as Post | null — no casting needed
  const post = await req.env.DB
    .prepare("SELECT * FROM posts WHERE id = ?")
    .bind(req.params.id)
    .first<Post>();

  if (!post) return res.status(404).json({ error: "Not found" });

  res.json(post);
});
```

<Note>
  D1 is **eventually consistent** for read replicas in multi-region deployments.
  Writes go to the primary region first and propagate to read replicas within
  seconds. If your application needs immediate read-your-write consistency after
  an insert or update, direct that follow-up read to the primary by using
  `wrangler d1` migrations and testing locally with `--local` to verify
  behavior before deploying.
</Note>
