家計簿アプリを作る #3:D1 への接続 + 収支一覧の表示
家計簿アプリ作成シリーズの第3回です。今回は Drizzle ORM 経由で D1 に接続し、収支データをトップページに一覧表示するところまでを実装しました。
ファイル構成
今回追加・変更するファイルは以下です。
src/
lib/
server/
db/
index.ts # Drizzle クライアントのヘルパー(sv add drizzle で生成済み)
routes/
+page.server.ts # 新規作成:D1 からデータ取得
+page.svelte # 変更:一覧表示
Step 1:Drizzle クライアントのヘルパーを確認する
npx sv add drizzle で src/lib/server/db/index.ts が自動生成されています。
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
export function createDb(d1: D1Database) {
return drizzle(d1, { schema });
}
createDb() に D1 のネイティブオブジェクトを渡すと、Drizzle クライアントが返ってきます。
ハマりポイント①:D1Database 型が見つからない
index.ts を開くと D1Database の型が見つからずエラーになることがあります。@cloudflare/workers-types を手動でインストールする必要があります。
npm install -D @cloudflare/workers-types
tsconfig.json に型を追加します。
{
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}
npx sv add drizzle で D1Database を使うファイルが生成されるのに、型パッケージが自動インストールされないのは不親切なポイントです。
Step 2:+page.server.ts を作成する
import type { PageServerLoad } from './$types';
import { createDb } from '$lib/server/db';
import { transactions } from '$lib/server/db/schema';
export const load: PageServerLoad = async ({ platform }) => {
const db = createDb(platform!.env.DB);
const allTransactions = await db.select().from(transactions);
return {
transactions: allTransactions
};
};
ハマりポイント②:platform.env.DB を直接使わない
最初、以下のように書いてしまいました。
const db = platform!.env.DB; // これは D1 のネイティブオブジェクト
const allTransactions = await db.select().from('transactions').all(); // エラー
platform.env.DB は D1 のネイティブオブジェクト(D1Database)で、Drizzle の API は使えません。必ず createDb() を通して Drizzle クライアントに変換してから使います。
ハマりポイント③:.from() には schema オブジェクトを渡す
.from() に渡すのはテーブル名の文字列ではなく、schema からインポートしたオブジェクトです。
// ❌ 文字列はダメ
await db.select().from('transactions');
// ✅ schema からインポートしたオブジェクトを渡す
import { transactions } from '$lib/server/db/schema';
await db.select().from(transactions);
ハマりポイント④:Drizzle では .all() は不要
D1 のネイティブ API では .prepare().all() という書き方をしますが、Drizzle では await db.select().from(transactions) だけで配列が返ります。.all() をつけると型エラーになり、さらに load 関数の戻り値の型推論が壊れて +page.svelte 側で data.transactions が {} 型に見えてしまいます。
// ❌ .all() は不要
await db.select().from(transactions).all();
// ✅
await db.select().from(transactions);
Step 3:+page.svelte で一覧表示する
<script lang="ts">
let { data } = $props();
</script>
<ul>
{#each data.transactions as transaction}
<li>{transaction.amount}</li>
<li>{transaction.type}</li>
<li>{transaction.category}</li>
<li>{transaction.memo}</li>
<li>{transaction.date}</li>
{/each}
</ul>
Step 4:テストデータを用意して動作確認する
プロジェクトルートに seed.sql を作成してテストデータを用意します。
INSERT INTO transactions (id, amount, type, category, memo, date) VALUES
('1', 5000, 'income', 'Salary', 'June salary', '2024-06-30'),
('2', 2000, 'expense', 'Groceries', 'Weekly groceries', '2024-06-29'),
('3', 1500, 'expense', 'Entertainment', 'Movie night', '2024-06-28'),
('4', 3000, 'income', 'Freelance', 'Project payment', '2024-06-27'),
('5', 1000, 'expense', 'Transportation', 'Monthly bus pass', '2024-06-26');
ローカル D1 に流し込みます。
npx wrangler d1 execute DB --local --file=seed.sql
開発サーバーを起動します。
npm run dev
ハマりポイント⑤:起動コマンドは npm run dev
npx wrangler dev を使うと以下のエラーが出ます。
✘ [ERROR] Missing entry-point to Worker script or to assets directory
wrangler dev は単体の Cloudflare Workers スクリプト向けのコマンドです。SvelteKit プロジェクトでは npm run dev を使います。
http://localhost:5173 を開いて INSERT したデータが表示されれば完了です。
まとめ
| ポイント | 内容 |
|---|---|
D1Database 型エラー |
@cloudflare/workers-types を手動インストールして tsconfig.json に追加 |
platform.env.DB |
D1 ネイティブオブジェクト。createDb() を通して Drizzle クライアントに変換する |
.from() の引数 |
文字列ではなく schema からインポートしたオブジェクト |
.all() |
Drizzle では不要。つけると型推論が壊れる |
| 起動コマンド | SvelteKit は npm run dev。wrangler dev ではない |
次回は収支の登録フォームを実装します。