Skip to content

Local development

D1 has fully-featured support for local development, running the same version of D1 as Cloudflare runs globally. Local development uses Wrangler, the command-line interface for Workers, to manage local development sessions and state.

Start a local development session

Local development sessions create a standalone, local-only environment that mirrors the production environment D1 runs in so that you can test your Worker and D1 before you deploy to production.

An existing D1 binding of DB would be available to your Worker when running locally.

To start a local development session:

  1. Confirm you are using wrangler v3.0+.

    Terminal window
    wrangler --version
    ⛅️ wrangler 3.0.0
  2. Start a local development session

    Terminal window
    wrangler dev
    ------------------
    wrangler dev now uses local mode by default, powered by 🔥 Miniflare and 👷 workerd.
    To run an edge preview session for your Worker, use wrangler dev --remote
    Your worker has access to the following bindings:
    - D1 Databases:
    - DB: test-db (c020574a-5623-407b-be0c-cd192bab9545)
    Starting local server...
    [mf:inf] Ready on http://127.0.0.1:8787/
    [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit

In this example, the Worker has access to local-only D1 database. The corresponding D1 binding in your wrangler.toml configuration file would resemble the following:

[[d1_databases]]
binding = "DB"
database_name = "test-db"
database_id = "c020574a-5623-407b-be0c-cd192bab9545"

Note that wrangler dev separates local and production (remote) data. A local session does not have access to your production data by default. To access your production (remote) database, pass the --remote flag when calling wrangler dev. Any changes you make when running in --remote mode cannot be undone.

Refer to the wrangler dev documentation to learn more about how to configure a local development session.

Develop locally with Pages

You can only develop against a local D1 database when using Cloudflare Pages by creating a minimal wrangler.toml in the root of your Pages project. This can be useful when creating schemas, seeding data or otherwise managing a D1 database directly, without adding to your application logic.

Your wrangler.toml should resemble the following:

# If you are only using Pages + D1, you only need the below in your wrangler.toml to interact with D1 locally.
[[d1_databases]]
binding = "DB" # Should match preview_database_id
database_name = "YOUR_DATABASE_NAME"
database_id = "the-id-of-your-D1-database-goes-here" # wrangler d1 info YOUR_DATABASE_NAME
preview_database_id = "DB" # Required for Pages local development

You can then execute queries and/or run migrations against a local database as part of your local development process by passing the --local flag to wrangler:

Terminal window
wrangler d1 execute YOUR_DATABASE_NAME \
--local --command "CREATE TABLE IF NOT EXISTS users ( user_id INTEGER PRIMARY KEY, email_address TEXT, created_at INTEGER, deleted INTEGER, settings TEXT);"

The preceding command would execute queries the local only version of your D1 database. Without the --local flag, the commands are executed against the remote version of your D1 database running on Cloudflare's network.

Persist data

Use wrangler dev --persist-to=/path/to/file to persist data to a specific location. This can be useful when working in a team (allowing you to share) the same copy, when deploying via CI/CD (to ensure the same starting state), or as a way to keep data when migrating across machines.

Users of wrangler 2.x must use the --persist flag: previous versions of wrangler did not persist data by default.

Test programmatically

Miniflare

Miniflare allows you to simulate a Workers and resources like D1 using the same underlying runtime and code as used in production.

You can use Miniflare's support for D1 to create D1 databases you can use for testing:

[[d1_databases]]
binding = "DB"
database_name = "test-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
const mf = new Miniflare({
d1Databases: {
DB: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
},
});

You can then use the getD1Database() method to retrieve the simulated database and run queries against it as if it were your real production D1 database:

const db = await mf.getD1Database("DB");
const stmt = db.prepare("SELECT name, age FROM users LIMIT 3");
const { results } = await stmt.all();
console.log(results);

unstable_dev

Wrangler exposes an unstable_dev() that allows you to run a local HTTP server for testing Workers and D1. Run migrations against a local database by setting a preview_database_id in your wrangler.toml configuration.

Given the below wrangler.toml configuration:

[[ d1_databases ]]
binding = "DB" # i.e. if you set this to "DB", it will be available in your Worker at `env.DB`
database_name = "your-database" # the name of your D1 database, set when created
database_id = "<UUID>" # The unique ID of your D1 database, returned when you create your database or run `
preview_database_id = "local-test-db" # A user-defined ID for your local test database.

Migrations can be run locally as part of your CI/CD setup by passing the --local flag to wrangler:

Terminal window
wrangler d1 migrations apply your-database --local

Usage example

The following example shows how to use Wrangler's unstable_dev() API to:

  • Run migrations against your local test database, as defined by preview_database_id.
  • Make a request to an endpoint defined in your Worker. This example uses /api/users/?limit=2.
  • Validate the returned results match, including the Response.status and the JSON our API returns.
import { unstable_dev } from "wrangler";
import type { UnstableDevWorker } from "wrangler";
describe("Test D1 Worker endpoint", () => {
let worker: UnstableDevWorker;
beforeAll(async () => {
// Optional: Run any migrations to set up your `--local` database
// By default, this will default to the preview_database_id
execSync(`NO_D1_WARNING=true wrangler d1 migrations apply db --local`);
worker = await unstable_dev("src/index.ts", {
experimental: { disableExperimentalWarning: true },
});
});
afterAll(async () => {
await worker.stop();
});
it("should return an array of users", async () => {
// Our expected results
const expectedResults = `{"results": [{"user_id": 1234, "email": "foo@example.com"},{"user_id": 6789, "email": "bar@example.com"}]}`;
// Pass an optional URL to fetch to trigger any routing within your Worker
const resp = await worker.fetch("/api/users/?limit=2");
if (resp) {
// https://jestjs.io/docs/expect#tobevalue
expect(resp.status).toBe(200);
const data = await resp.json();
// https://jestjs.io/docs/expect#tomatchobjectobject
expect(data).toMatchObject(expectedResults);
}
});
});

Review the unstable_dev() documentation for more details on how to use the API within your tests.