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 |
binding に DB を指定しておくと、コード内での参照が 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不要で使えます。DB は wrangler.jsonc の binding と一致させます。
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.svelte も data.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バインディングを使う場合の起動コマンド |