家計簿アプリを作る #4:収支の登録フォームを実装する

家計簿アプリ作成シリーズの第4回です。今回は SvelteKit の Form Actions を使って収支の登録フォームを実装します。

ファイル構成

src/routes/
  transactions/
    new/
      +page.server.ts  # 新規作成:Form Action でデータを登録
      +page.svelte     # 新規作成:登録フォーム

登録フォームは /transactions/new に配置しました。後々 /transactions で一覧、/transactions/[id] で詳細・編集と自然に拡張できる構成です。

+page.server.ts:Form Action を実装する

import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { createDb } from '$lib/server/db';
import { transactions } from '$lib/server/db/schema';

export const actions: Actions = {
  create: async ({ request, platform }) => {
    const formData = await request.formData();
    const amount = formData.get('amount');
    const type = formData.get('type');
    const category = formData.get('category');
    const memo = formData.get('memo') || '';
    const date = formData.get('date');

    if (!amount || !type || !category || !date) {
      return fail(400, { error: 'All fields except memo are required.' });
    }

    const db = createDb(platform!.env.DB);

    await db.insert(transactions).values({
      amount: Number(amount),
      type: String(type),
      category: String(category),
      memo: String(memo),
      date: String(date)
    });

    redirect(303, '/');
  }
};

ポイント:

  • formData.get() の戻り値は FormDataEntryValue | null なので、必須項目は if (!amount || ...) でバリデーションする
  • バリデーションエラーは fail(400, { error: '...' }) で返すと、フォーム側で受け取れる
  • memo は任意入力のため || '' でデフォルト空文字にしている
  • Drizzle の db.insert().values() でデータを挿入する
  • 登録後は redirect(303, '/') でトップページへ戻る

+page.svelte:登録フォームを作る

<script lang="ts">
  import { enhance } from "$app/forms";

  let { form } = $props();
</script>

<form method="POST" action="?/create" use:enhance>
  <label>
    金額:
    <input type="number" name="amount" required />
  </label>
  <label>
    種類:
    <select name="type" required>
      <option value="income">収入</option>
      <option value="expense">支出</option>
    </select>
  </label>
  <label>
    カテゴリ:
    <input type="text" name="category" required />
  </label>
  <label>
    メモ:
    <input type="text" name="memo" />
  </label>
  <label>
    日付:
    <input type="date" name="date" required />
  </label>
  <button type="submit">追加</button>

  {#if form?.error}
    <p style="color: red">{form.error}</p>
  {/if}
</form>

ポイント:

  • use:enhance で JavaScript が有効な環境ではページ全体のリロードなしに送信できる
  • action="?/create"+page.server.tscreate アクションを呼び出す
  • form?.error でサーバーから返ったエラーメッセージを表示する

動作確認

npm run dev で起動後、http://localhost:5173/transactions/new にアクセスしてフォームから登録し、トップページの一覧に反映されることを確認します。

まとめ

ポイント 内容
URL 設計 /transactions/new に配置。後の拡張を見越した構成
Form Actions export const actions に関数を定義する
バリデーション fail(400, {...}) でエラーをフォームに返す
データ挿入 db.insert(transactions).values({...})
リダイレクト redirect(303, '/') でトップへ戻る
use:enhance ページリロードなしでフォーム送信できる

次回は収支の削除・編集を実装します。

← トップページに戻る