Tidbits on software development, technology, and other geeky stuff

SvelteKit with Drizzle and Cloudflare D1

I recently started a little side project for my wife and decided to use SvelteKit to build it and Cloudflare to host it. Along the way I decided to use Drizzle ORM along with Cloudflare D1 to store the data. I was really happy with how everything worked but I did run into a few issues: using a local SQLite database for development and applying migrations to both dev and production environments. After some fiddling I got things working nicely and wanted to document my setup here.

Instructions

First off, you’ll want to follow the Get Started with Drizzle and SQLite docs to get a local SQLite database setup and working within SvelteKit. This will involve installing some npm packages and creating a few files including the drizzle.config.ts file and the schema.ts file. Be sure to run npx drizzle-kit generate && npx drizzle-kit migrate to create and run the initial migration file from ./drizzle folder. The guide SvelteKit with SQLite and Drizzle might be helpful here.

Before proceeding with the instructions below, your app should be setup to work with a local SQLite database using Drizzle.

Now, you’ll want to get your app configured to work with Cloudflare Pages. Follow the SvelteKit Cloudflare Pages instructions. You will basically install a npm package and and modify the svelte.config.js file.

svelte.config.js changes

I did find that the instructions for svelte.config.js needed to be changed slightly. platformProxy.persist needs to be set to true in order to persist the local SQLite database. Also, change configPath to "wrangler.json" (from "wrangler.toml").

platformProxy: {
  // Other config ...
  configPath: "wrangler.json";
  persist: true;
}

Install and configure wrangler

Install the wrangler CLI tool with npm install wrangler --save-dev.

Then, create a wrangler.json file with the following contents in the root of your project.

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my_project_name",
  "compatibility_date": "2025-02-04",
  "pages_build_output_dir": ".svelte-kit/cloudflare",
  "d1_databases": [
    {
      "migrations_dir": "./drizzle",
      "binding": "DB",
      "database_name": "my-d1-database-name",
      "database_id": "database_id_TBD"
    }
  ]
}

Create remote D1 database

Now, you’ll create your D1 database on Cloudflare using the wrangler CLI.

Run the following:

npx wrangler d1 create my-d1-database-name

The output of this command will provide you with JSON config for the wrangler.json file. Just copy paste the value of database_id and update your wrangler.json file, replacing the "database_id_TBD" value.

Create local “D1” (SQLite) database

To create a local SQLite database used for development, you will first need to find the name of the initial Drizzle migration file. This file should be named in format 0000_[migration_file].sql and be located in the ./drizzle directory.

Now, run the following command, specifying the specific migration file you located.

npx wrangler d1 execute my-d1-database-name --local --file=./drizzle/0000_[migration_file].sql

A .sqlite file will be created in .wrangler/state/v3/d1/miniflare-D1DatabaseObject/. You should find this file and note its name.

drizzle.config.ts

The drizzle.config.ts file is used by Drizzle when working with migrations. You need to change the dbCredentials.url to the location of the SQLite database created by wrangler in the previous step.

Your drizzle.config.ts file will look something like this:

import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/lib/server/schema",
  dialect: "sqlite",
  out: "./drizzle",
  dbCredentials: {
    url: ".wrangler/state/v3/d1/miniflare-D1DatabaseObject/e7352547963de7050bd7d94658afc4fe78b61811b7815da12d90be8e863abf4d.sqlite",
  },
});

SvelteKit modifications

Update your app.d.ts file to include the env: Env field. Cloudflare (and wrangler locally) will inject the DB variable binding into the env object which will be used to connect to D1.

declare global {
    namespace App {
        interface Platform {
            env: Env;
        }
    }
}

export {};

Now, anywhere you want to interact with D1 in your SvelteKit code, you’ll want to import { drizzle } from 'drizzle-orm/d1'; and instantiate a db object by passing in the DB binding like this: const db = drizzle(platform.env.DB);.

Here is an example of how you would set things up and select from a users table:

import { drizzle } from 'drizzle-orm/d1';

export const actions = {
  fetchUsers: async ({ request, platform }) => {
    const db = drizzle(platform.env.DB);
    const users = await db.select().from("users");
    ...
  }
} satisfies Actions;

package.json

Finally, add the following scripts to package.json to make it easier to work with the database and deploy the site.

{
  "scripts": {
    ...
    "db:migrate:local": "drizzle-kit migrate",
    "db:migrate:prod": "wrangler d1 migrations apply DB --remote",
    "deploy": "npm run build && npm run db:migrate:prod && wrangler pages deploy"
  }
}

Note: for db:migrate:prod, we call wrangler d1 migrations apply which uses the wrangler CLI (rather than drizzle-kit!) to apply the migrations to D1. wrangler is able to read and work with the migrations created by Drizzle. I found ways for drizzle-kit to do this directly, but it was more complicated and I found this to be the easiest way to do it.

Starting up local development

Run npm run db:migrate:local to run any pending migrations you may have for the local database. Then run npm run dev to start up the local development server. All interactions with Drizzle should now be targeting the local SQLite database, using the DB binding.

Deploying to Cloudflare

Run npm run deploy to build the site, run any pending migrations on Cloudflare D1, and deploy the site to Cloudflare.