LoginSignup
5
4

Cloudflare D1 を試そう with SvelteKit

Posted at

はじめに :sparkles:

Cloudflare D1 は、Cloudflare が提供する、Cloudflare Workers 用に設計されたデータベースです。
その特徴については、下記記事が大変よくまとまっています。
D1 ってなに?って方はまずこちらを読んでみることをおすすめします。

今回は、Cloudflare D1 と SvelteKit を組み合わせて、簡単にハンズオンを行ってみたいと思います。

検証環境 :computer:

  • WSL2 Ubuntu 22.04
  • bun 1.0.0

package.json は最終的に次のようになりました。

  "devDependencies": {
    "@sveltejs/adapter-auto": "^2.0.0",
    "@sveltejs/adapter-cloudflare-workers": "^1.1.4",
    "@sveltejs/kit": "^1.20.4",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "drizzle-kit": "^0.19.13",
    "drizzle-orm": "^0.28.6",
    "eslint": "^8.28.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-svelte": "^2.30.0",
    "prettier": "^2.8.0",
    "prettier-plugin-svelte": "^2.10.1",
    "svelte": "^4.0.5",
    "svelte-check": "^3.4.3",
    "tslib": "^2.4.1",
    "typescript": "^5.0.0",
    "vite": "^4.4.2",
    "wrangler": "^3.9.0"
  },

ORM については必須ではありませんが、今回は Drizzle ORM を利用しました。

プロジェクトの作成 :hammer:

今回は svelte-kit のテンプレートを利用してプロジェクトを作成していきます。

bun create svelte@latest d1-playground
cd d1-playground
bun install

bun install v1.0.0 (822a00c4)
 + @sveltejs/adapter-auto@2.1.0
 + @sveltejs/kit@1.25.0
 + @typescript-eslint/eslint-plugin@6.7.2
 + @typescript-eslint/parser@6.7.2
 + eslint@8.50.0
 + eslint-config-prettier@8.10.0
 + eslint-plugin-svelte@2.33.2
 + prettier@2.8.8
 + prettier-plugin-svelte@2.10.1
 + svelte@4.2.1
 + svelte-check@3.5.2
 + tslib@2.6.2
 + typescript@5.2.2
 + vite@4.4.9

 218 packages installed [3.60s]

次に wrangler をインストールします。

bun add -D wrangler

bun add v1.0.0 (822a00c4)

 installed wrangler@3.9.0 with binaries:
  - wrangler
  - wrangler2


 38 packages installed [2.84s]

同時に wrangler.toml も作成します。

name = "d1-playground"

main = "./.cloudflare/worker.js"
site.bucket = "./.cloudflare/public"

build.command = "npm run build"

compatibility_date = "2023-01-01"
workers_dev = true

これで準備は完了です。

データベースの作成 :floppy_disk:

D1 にデータベースを作成する場合は、wrangler d1コマンドを利用します。

bun wrangler d1 create d1-playground

--------------------
🚧 D1 is currently in open alpha and is not recommended for production data and traffic
🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose
🚧 To request features, visit https://community.cloudflare.com/c/developers/d1
🚧 To give feedback, visit https://discord.gg/cloudflaredev
--------------------

✅ Successfully created DB 'd1-playground' in region APAC
Created your database using D1's new storage backend. The new storage backend is not yet recommended for
production workloads, but backs up your data via point-in-time restore.

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "d1-playground"
database_id = "YOUR_DATABASE_ID"

wrangler.toml にも追記しましょう。

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "d1-playground"
database_id = "YOUR_DATABASE_ID"

drizzle cli と drizzle-orm を追加 :wrench:

D1 にテーブルを作成する際に ORM は必須ではありません。
そのため、この章は読み飛ばしていただいて構いません。
今回は Drizzle ORM を利用して、簡単にデータベースを操作できるようにしていきます。
詳しくは下記記事が参考になります。

bun add drizzle-kit drizzle-orm -D

bun add v1.0.0 (822a00c4)

 installed drizzle-kit@0.19.13 with binaries:
  - drizzle-kit
 installed drizzle-orm@0.28.6


 44 packages installed [3.27s]

スキーマの定義は参考記事よりお借りしています。

/*
  DO NOT RENAME THIS FILE FOR DRIZZLE-ORM TO WORK
*/
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const users = sqliteTable("users", {
  id: integer("id").primaryKey().notNull(),
  name: text("name").notNull(),
});

マイグレーションの実行 :tools:

参考記事ではbetter-sqlite3がマイグレーションに必要とされていますが、私の環境ではインストールせずに実行できました。
もし理由をご存じのかたがいらっしゃいましたら、ご教授いただけると幸いです。

bun drizzle-kit generate:sqlite --out migrations --schema src/schema.ts

drizzle-kit: v0.19.13
drizzle-orm: v0.28.6

1 tables
users 2 columns 0 indexes 0 fks

[✓] Your SQL migration file ➜ migrations/0000_fast_patch.sql 🚀

マイグレーションの実行

bun wrangler d1 migrations apply d1-playground --local[WARNING] Processing wrangler.toml configuration:

    - D1 Bindings are currently in alpha to allow the API to evolve before general availability.
      Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose
      Note: Run this command with the environment variable NO_D1_WARNING=true to hide this message

      For example: `export NO_D1_WARNING=true && wrangler <YOUR COMMAND HERE>`


--------------------
🚧 D1 is currently in open alpha and is not recommended for production data and traffic
🚧 Please report any bugs to https://github.com/cloudflare/workers-sdk/issues/new/choose
🚧 To request features, visit https://community.cloudflare.com/c/developers/d1
🚧 To give feedback, visit https://discord.gg/cloudflaredev
--------------------

Migrations to be applied:
┌─────────────────────┐
│ name                │
├─────────────────────┤
│ 0000_fast_patch.sql │
└─────────────────────┘
✔ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes
🌀 Mapping SQL input into an array of statements
🌀 Loading YOUR_DATABASE_ID from .wrangler/state/v3/d1
┌─────────────────────┬────────┐
│ name                │ status │
├─────────────────────┼────────┤
│ 0000_fast_patch.sql │ ✅       │
└─────────────────────┴────────┘

これで ローカルにテーブルが作成されました。
ここで動作確認をしたい方は、次のコマンドで任意の SQL を実行することが可能です。

wrangler d1 execute d1-playground --local --command='insert into users (name) values ("test")'
wrangler d1 execute d1-playground --local --command='select * from users'

SvelteKit の構築 :rocket:

まずは Cloudflare Workers 上で実行するための設定を行います。
※ wrangler を利用してローカルで動作確認する場合も必要です。

アダプターの追加と設定 :gear:

bun add -D @sveltejs/adapter-cloudflare-workers
import adapter from "@sveltejs/adapter-cloudflare-workers";

export default {
  kit: {
    adapter: adapter(),
  },
};

さて、ここからは SvelteKit の実装を行っていきます。

D1 クライアントの作成 :electric_plug:

drizzle-orm/d1 を利用して D1 クライアントを作成します。

client を取得するためには 次のようなコードを記述します。

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

export interface Env {
  <BINDING_NAME>: D1Database;
}

export default {
  async fetch(request: Request, env: Env) {
    const db = drizzle(env.<BINDING_NAME>);
    const result = await db.select().from(users).all()
    return Response.json(results);
  },
};

今回は SvelteKit を利用しているため、環境変数を取得する場合は、platform プロパティを経由する必要があります。

export async function POST({ platform }) {
  const DB = platform?.env?.DB;
}

また、platformから取得する変数の型についても、定義する必要があります。

typescript app.d.ts
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
  namespace App {
    // interface Error {}
    // interface Locals {}
    // interface PageData {}
    interface Platform {
      env?: {
        DB: D1Database;
      };
    }
  }
}

export {};

D1 クライアントを作成するためには、環境変数から取得した platform?.env?.DBdrizzle 関数の引数に渡します。
今回は createD1Client という関数を作成して、d1client.ts にまとめておきます。

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

export const createD1Client = (d1: D1Database | undefined) => {
  if (!d1) {
    throw new Error(`Database not found`);
  }
  return drizzle(d1);
};

では、実際にデータ取得、データ更新を実装してみましょう。

データ取得 :mag:

データ取得は下記のように実装できます。

import { createD1Client } from "$lib/server/d1client";
import { users } from "../schema";

const d1Client = createD1Client(platform?.env?.DB);
const result = await d1Client.select().from(users).all();

データ更新 :pencil2:

データ更新は下記のように実装できます。

import { createD1Client } from "$lib/server/d1client";
import { users } from "../schema";

const d1Client = createD1Client(platform?.env?.DB);
const result = await d1Client.insert(users).values({ name: "John" }).execute();

メソッドチェーンを利用してクエリを記述できる点は、 ORM を利用する場合のメリットですね。

全体の実装 :space_invader:

最後に実装全体をまとめておきます。
初期ロード時にデータを取得し、フォームからデータを更新することができるようになりました。

注意としては、bun dev ではなく wrangler dev を実行する必要があります。
お好みでScriptを編集するとよいでしょう。

+page.svelte :flower_playing_cards:

<script lang="ts">
  import type { PageData, ActionData } from "./$types";
  export let data: PageData;
  export let form: ActionData;
</script>

<h1>Regist User</h1>

{#if form && !form.success}
  <p>{form.error}</p>
{/if}

<form method="POST">
  <label>Name<input name="name" type="text" /></label>
  <button type="submit">Submit</button>
</form>

{#each data.users as user}
  <p>{user.id}:{user.name}</p>
{/each}

+page.server.ts :art:

import type { PageServerLoad, Actions } from "./$types";
import { createD1Client } from "$lib/server/d1client";
import { users } from "../schema";

export const load = (async ({ platform }) => {
  const d1Client = createD1Client(platform?.env?.DB);
  const result = await d1Client.select().from(users).all();
  return { users: result };
}) satisfies PageServerLoad;

export const actions = {
  default: async ({ platform, request }) => {
    const data = await request.formData();
    const name = data.get("name");
    if (!name) {
      return { success: false, error: "Name is required" };
    }
    const d1Client = createD1Client(platform?.env?.DB);
    await d1Client.insert(users).values({ name: name.toString() }).execute();
    return { success: true };
  },
} satisfies Actions;

d1client.ts :tropical_drink:

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

export const createD1Client = (d1: D1Database | undefined) => {
  if (!d1) {
    throw new Error(`Database not found`);
  }
  return drizzle(d1);
};

まとめ :tomato:

今回は Cloudflare D1 と SvelteKit を組み合わせて、簡単な CRUD を実装してみました。
D1 はまだアルファ版ということもあり、プロダクト運用には向いていないかもしれませんが、趣味であれば関係ありません。
是非皆さんも試してみてはいかがでしょうか!

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4