# D1

> Use SQL tables, prepared statements, and transactional batches for application records.

<div class="ce-block-head">
<p class="ce-block-lead">Use SQL tables, prepared statements, and transactional batches for application records.</p>
<ul class="ce-facts">
<li><span>Library</span> <code>Runtime.Workers</code></li>
<li><span>npm</span> <code>@cloudflare/workers-types</code> 5.20260906.1</li>
</ul>
</div>

## Database and schema

Create a D1 database, apply its schema or migrations, and attach it to the consuming Worker as a `D1Database` binding. Keep that database lifecycle separate from deploying a new Worker version. The [account setup example](../control-plane/account-setup.md) creates a database through the management client.

Bind values as statement parameters. F# result types describe the expected row shape; they do not migrate the schema or validate arbitrary SQL results. The examples below demonstrate reads and a batch that commits related inserts together.

## Guestbook

D1 is a SQL database. `prepare` and `bind` build a statement with `?` placeholders. `run` executes the insert, and `all` returns the rows as `Entry` records. The daily D1 allowance on the free plan is 5 million rows read and 100,000 rows written.

```fsharp
open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Entry = {| name: string; message: string |}

type Env =
    abstract DB: Workers.D1Database

let insertSql = "INSERT INTO entries (name, message) VALUES (?, ?)"
let latestSql = "SELECT name, message FROM entries ORDER BY id DESC LIMIT 20"

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            match request.``method`` with
            | "POST" ->
                let! entry = request.json<Entry>() |> Async.AwaitPromise
                let insert = env.DB.prepare(insertSql).bind(entry.name, entry.message)
                let! result = insert.run() |> Async.AwaitPromise
                return Workers.Exports.Response.json {| id = result.meta.last_row_id |}
            | _ ->
                let! result = env.DB.prepare(latestSql).all<Entry>() |> Async.AwaitPromise
                return Workers.Exports.Response.json result.results
        }
        |> Async.StartAsPromise
        |> U2.Case1)
```

<div class="ce-needs"><p><strong>Needs</strong> a <code>DB</code> binding to a D1 database, with the <code>entries</code> table the queries use.</p></div>

<details class="ce-js"><summary>Emitted JavaScript</summary>

```javascript
import { awaitPromise, startAsPromise } from "./fable_modules/fable-library-js.5.13.0/Async.js";
import { singleton } from "./fable_modules/fable-library-js.5.13.0/AsyncBuilder.js";

export const insertSql = "INSERT INTO entries (name, message) VALUES (?, ?)";

export const latestSql = "SELECT name, message FROM entries ORDER BY id DESC LIMIT 20";

export const worker = {
    fetch: (request, env, _arg) => startAsPromise(singleton.Delay(() => ((request.method === "POST") ? singleton.Bind(awaitPromise(request.json()), (_arg_1) => {
        const entry = _arg_1;
        const insert = env.DB.prepare(insertSql).bind(entry.name, entry.message);
        return singleton.Bind(awaitPromise(insert.run()), (_arg_2) => singleton.Return(globalThis.Response.json({
            id: _arg_2.meta.last_row_id,
        })));
    }) : singleton.Bind(awaitPromise(env.DB.prepare(latestSql).all()), (_arg_3) => singleton.Return(globalThis.Response.json(_arg_3.results)))))),
};

export default worker;
```

</details>

## Order Checkout

`batch` sends an order and its items to D1 in one call. Cloudflare's [D1 documentation](https://developers.cloudflare.com/d1/worker-api/d1-database/) defines batched statements as SQL transactions, so if one insert fails, D1 rolls back the order and all of its items. The item statement is prepared once and bound once per item.

```fsharp
open Fable.Core

module Workers = FSharp.CloudEdge.Runtime.Workers

type Order = {| email: string; items: {| sku: string; quantity: float |}[] |}

type Env =
    abstract DB: Workers.D1Database

let orderSql = "INSERT INTO orders (id, email) VALUES (?, ?)"
let itemSql = "INSERT INTO order_items (order_id, sku, quantity) VALUES (?, ?, ?)"

[<ExportDefault>]
let worker: Workers.ExportedHandler<Env, obj, obj, obj> =
    Workers.ExportedHandler.Create(fetch = fun request env _ ->
        async {
            let! order = request.json<Order>() |> Async.AwaitPromise
            let orderId = Workers.Exports.crypto.randomUUID()
            let addItem = env.DB.prepare itemSql
            let statements =
                [| env.DB.prepare(orderSql).bind(orderId, order.email)
                   for item in order.items do
                       addItem.bind(orderId, item.sku, item.quantity) |]
            let! _ = env.DB.batch statements |> Async.AwaitPromise
            return Workers.Exports.Response.json {| orderId = orderId |}
        }
        |> Async.StartAsPromise
        |> U2.Case1)
```

<div class="ce-needs"><p><strong>Needs</strong> the same <code>DB</code> binding, with <code>orders</code> and <code>order_items</code> tables.</p></div>

## Search over stored content

The [Hybrid Search recipe](../hybrid-search.md) combines D1 full-text search with a [Vectorize](vectorize.md) index. D1 holds the keyword index and content records; Vectorize provides similarity search over embeddings.


## Help verify the binding

Useful checks include row decoding, null values, statement parameters, batch rollback, and migrations. A compiling consumer does not establish hosted behavior. Record the package version, configuration, and observed result using the [verification guide](../../guide/verify-bindings.md).

## Related Pages

- [Data & Analytics](index.md)
- [Account setup](../control-plane/account-setup.md)
- [Worker bindings](../control-plane/worker-upload.md)

## NuGet packages

[Runtime.Workers 0.1.0](https://www.nuget.org/packages/FSharp.CloudEdge.Runtime.Workers/0.1.0).

See [installation and release availability](../../guide/packages.md).
