d1
Typed wrappers over D1's prepare().bind(). Raw SQL in, the D1 result out. Not an ORM.
On this page
What it does
d1 runs SQL on Cloudflare D1. You write SQL with ? placeholders. You get back the object D1
returned. There is no query builder and no schema mapping.
How it works
query, first and run are one line each: prepare(sql).bind(...params), then all(), first()
or run(). batch needs real prepared statements, so prepare(env) hands you the database to build
them. It issues no query.
The config is a map of instances with a default, as in kv.
Config
pluginConfigs.d1, keyed by your logical name. The default is {}.
| Option | Type | Default | Description |
|---|---|---|---|
[key].name | string | required | The Cloudflare database. A stage other than production suffixes it at deploy. |
[key].binding | string | required | The key on env. |
[key].migrations | string | none | The directory of .sql migrations. Deploy metadata: never read at request time. |
[key].default | boolean | none | Marks the default when there are several. |
API
Mounted at app.d1, reachable with require(d1Plugin) in a handler.
| Method | Signature | Description |
|---|---|---|
query | <T>(env, sql: string, ...params: unknown[]) => Promise<D1Result<T>> | All rows, in .results. |
first | <T>(env, sql: string, ...params: unknown[]) => Promise<T | null> | The first row, or null. |
run | (env, sql: string, ...params: unknown[]) => Promise<D1Result> | A write or DDL. Read .meta. |
batch | (env, statements: D1PreparedStatement[]) => Promise<D1Result[]> | Statements in one atomic round trip, results in order. |
prepare | (env) => D1Database | The database, to build statements for batch. |
use | (key: string) => D1DatabaseApi | The same five methods on another database. |
deployManifest | () => { kind: "d1"; name: string; binding: string; migrations?: string }[] | Build-time metadata. Takes no env. |
Events
Emits
None.
Listens to
None.
Depends on
- bindings: resolves the database on every call.
Example
import { createApp, d1Plugin, endpoint } from "@moku-labs/worker";
type Task = { id: number; title: string };
export const app = createApp({
plugins: [d1Plugin],
pluginConfigs: {
d1: { main: { name: "tasks-db", binding: "DB", migrations: "db/migrations" } },
server: {
endpoints: [
endpoint("/tasks").get(async ({ env, require }) => {
const { results } = await require(d1Plugin).query<Task>(env, "SELECT id, title FROM tasks");
return Response.json(results);
}),
endpoint("/tasks/{id}").get(async ({ params, env, require }) => {
const task = await require(d1Plugin).first<Task>(env, "SELECT id, title FROM tasks WHERE id = ?", params.id);
return task === null ? new Response("Not Found", { status: 404 }) : Response.json(task);
}),
endpoint("/tasks").post(async ({ request, env, require }) => {
const { meta } = await require(d1Plugin).run(env, "INSERT INTO tasks (title) VALUES (?)", await request.text());
return Response.json({ id: meta.last_row_id }, { status: 201 });
}),
endpoint("/tasks/{from}/move/{to}").post(async ({ params, env, require }) => {
const d1 = require(d1Plugin);
const db = d1.prepare(env);
await d1.batch(env, [
db.prepare("UPDATE tasks SET list = ? WHERE list = ?").bind(params.to, params.from),
db.prepare("UPDATE lists SET moved = moved + 1 WHERE id = ?").bind(params.from)
]);
return new Response(null, { status: 204 });
})
]
}
}
});- Both statements commit, or neither does.
Migrations and seeds
d1 only stores the migrations path. deploy acts on it:
| Where | When |
|---|---|
| Local | Each dev start, while migrateLocal is on. Also when seed runs locally. |
| Remote | After a live deploy, only with deploy({ migration: true }). |
- A plain deploy does not migrate production. Pass
migration: true, or the new code runs on the old schema. - Row types are your word.
<T>is forwarded to D1. Nothing checks the rows against it. batchtakes statements, not SQL. Build them fromprepare(env)on the same database.
Related
- Build & deploy: migrations and seeds in the deploy script.
- deploy plugin: the seed config.