SvelteKitにCloudflare D1を導入してDBからデータを取得した話

$lib/posts.ts に書いていた静的なデータをCloudflare D1(サーバーレスSQLite)に移行しました。 この記事ではDB作成からマイグレーション、SvelteKit側でのデータ取得までの手順をまとめます。

D1とは

CloudflareがEdge環境向けに提供するサーバーレスSQLiteデータベースです。Cloudflare Workersと同じ環境で動作し、wrangler コマンドで操作します。

ファイル構成

sveltekit-blog/
  migrations/
    001_posts.sql         # 新規作成:テーブル定義とデータ投入
  src/
    app.d.ts              # 変更:Platform型を追加
    routes/
      +page.server.ts     # 新規作成:記事一覧をD1から取得
      +page.svelte        # 変更:data.postsを使うよう修正
      posts/[id]/
        +page.server.ts   # 変更:+page.tsからリネームしてD1対応
  wrangler.jsonc          # 変更:D1バインディングを追加

Step 1: D1データベースを作成する

npx wrangler d1 create sveltekit-blog-db

実行中に以下を聞かれます。

質問 回答
Would you like Wrangler to add it on your behalf? y
What binding name would you like to use? DB
For local dev, do you want to connect to the remote resource? n

bindingDB を指定しておくと、コード内での参照が platform.env.DB とシンプルになります。

Step 2: wrangler.jsonc にバインディングが追加される

wranglerが自動で wrangler.jsonc に追記します。

{
  // ...既存の設定...
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "sveltekit-blog-db",
      "database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
  ]
}

binding の値がコード内でD1を参照するときのキーになります。

Step 3: app.d.ts に Platform 型を追加

D1を型安全に使うために Platform インターフェースを追加します。

// src/app.d.ts
declare global {
  namespace App {
    interface Locals {
      user: { name: string } | null;
    }
    interface Platform {
      env: {
        DB: D1Database;
      };
    }
  }
}

export {};

D1Database はwranglerが提供する型で、import不要で使えます。DBwrangler.jsoncbinding と一致させます。

Step 4: マイグレーションファイルを作成する

プロジェクトルートに migrations/ フォルダを作り、SQLファイルを置きます。

-- migrations/001_posts.sql
CREATE TABLE IF NOT EXISTS posts (
  id      INTEGER PRIMARY KEY AUTOINCREMENT,
  title   TEXT NOT NULL,
  body    TEXT NOT NULL
);

INSERT INTO posts (title, body) VALUES
  ('SvelteKitとは', 'SvelteKitはフルスタックフレームワークです。'),
  ('ルーティング入門', 'ファイルベースのルーティングを使います。'),
  ('load関数の使い方', 'データ取得はload関数で行います。');

ファイル名の 001_ は実行順を明示するための慣例です。CREATE TABLE IF NOT EXISTS にしておくと誤って2回実行しても安全です。

Step 5: ローカルDBにマイグレーションを実行する

npx wrangler d1 execute sveltekit-blog-db --local --file=migrations/001_posts.sql

--local フラグで .wrangler/state/ 配下のローカルSQLiteに対して実行します。リモートのCloudflare D1には影響しません。

データが入ったか確認します。

npx wrangler d1 execute sveltekit-blog-db --local --command="SELECT * FROM posts"

Step 6: 記事一覧ページをD1対応にする

src/routes/+page.server.ts を新規作成します。

// src/routes/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ platform }) => {
  const db = platform?.env.DB;
  const result = await db?.prepare('SELECT * FROM posts').all();

  return {
    posts: result?.results ?? []
  };
};

platform.env.DB がD1への接続口です。platform はローカル開発時に undefined になる可能性があるため ?. でアクセスします。.all() で全件取得し、result.results に行の配列が入っています。

src/routes/+page.sveltedata.posts を使うよう修正します。

<!-- src/routes/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<div class="max-w-2xl mx-auto p-8">
  <h1 class="text-3xl font-bold mb-6">記事一覧</h1>

  <ul class="space-y-2">
    {#each data.posts as post}
      <li>
        <a href="/posts/{post.id}" class="text-blue-600 hover:underline">
          {post.title}
        </a>
      </li>
    {/each}
  </ul>
</div>

Step 7: 記事詳細ページをD1対応にする

+page.ts+page.server.ts にリネームして内容を書き換えます。D1はサーバー側でしか使えないため、+page.server.ts にする必要があります。

// src/routes/posts/[id]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, platform }) => {
  const db = platform?.env.DB;
  const post = await db?.prepare('SELECT * FROM posts WHERE id = ?')
    .bind(params.id)
    .first();

  if (!post) error(404, '記事が見つかりません');

  return { post };
};

.prepare('... WHERE id = ?').bind(params.id) のようにプレースホルダー ? に値を .bind() で渡します。SQLを文字列結合で組み立てるとSQLインジェクションの危険があるため、必ずこの書き方にします。.first() で1件だけ取得します。

ファイルをリネームした後は型定義を再生成します。

npx svelte-kit sync

動作確認

D1を使う場合は npm run dev ではなく npx wrangler dev で起動します。

npx wrangler dev

http://localhost:8787 で記事一覧と詳細ページが表示されれば完了です。

まとめ

概念 内容
wrangler d1 create D1データベースを作成するコマンド
binding コード内でD1を参照するときのキー名
Platform インターフェース app.d.ts でCloudflareバインディングの型を定義
migrations/ SQLファイルでテーブル定義を管理するフォルダ
--local フラグ ローカルSQLiteに対して実行(リモートに影響しない)
.prepare().all() 複数件取得
.prepare().first() 1件取得
.bind() プレースホルダーに値をセット(SQLインジェクション対策)
npx wrangler dev D1などCloudflareバインディングを使う場合の起動コマンド
← トップページに戻る