家計簿アプリを作る #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 drizzlesrc/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 drizzleD1Database を使うファイルが生成されるのに、型パッケージが自動インストールされないのは不親切なポイントです。

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 devwrangler dev ではない

次回は収支の登録フォームを実装します。

← トップページに戻る