さとまたwiki

フォームアクション

サーバーサイドでフォームを処理する

フォームアクションとは?

日常での例え:窓口での申請処理

申請書(フォーム)に記入して窓口に提出

職員(サーバー)が内容をチェック・処理

結果を返す(成功/エラー)

なぜフォームアクションが便利?

従来の方法:fetch() で自分でAPIを呼び出す → コードが複雑に

フォームアクション:HTMLの <form> を使うだけ!

  • JavaScriptが無効でも動作する(アクセシビリティ)
  • バリデーションエラーと入力値の復元が簡単
  • use:enhance でJSによる機能強化も可能

基本的なフォームアクション

アクションの定義

+page.server.tsにactionsを定義

typescript
// src/routes/login/+page.server.ts
export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    // バリデーション
    if (!email || !password) {
      return { success: false, error: '入力してください' };
    }

    // ログイン処理...

    return { success: true };
  }
};
プレビュー
// form の method="POST" で
// actions.default が呼ばれる

フォームの実装

method=POSTで送信

svelte
<!-- src/routes/login/+page.svelte -->
<script>
  let { form } = $props();
</script>

<form method="POST">
  <input name="email" type="email" required>
  <input name="password" type="password" required>
  <button type="submit">ログイン</button>
</form>

{#if form?.error}
  <p class="error">{form.error}</p>
{/if}

{#if form?.success}
  <p class="success">ログイン成功!</p>
{/if}
プレビュー

名前付きアクション

複数のアクション

action属性で指定

typescript
// src/routes/posts/+page.server.ts
export const actions = {
  create: async ({ request }) => {
    const data = await request.formData();
    const title = data.get('title');
    await createPost({ title });
    return { success: true };
  },

  delete: async ({ request }) => {
    const data = await request.formData();
    const id = data.get('id');
    await deletePost(id);
    return { deleted: true };
  }
};
プレビュー
actions.create
actions.delete

名前付きアクションの呼び出し

action=?/アクション名

svelte
<!-- 投稿作成フォーム -->
<form method="POST" action="?/create">
  <input name="title" required>
  <button>投稿</button>
</form>

<!-- 削除フォーム -->
<form method="POST" action="?/delete">
  <input type="hidden" name="id" value={post.id}>
  <button>削除</button>
</form>
プレビュー

バリデーション

fail関数でエラーを返す

入力値を保持してエラー表示

typescript
// src/routes/register/+page.server.ts
import { fail } from '@sveltejs/kit';

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = data.get('email')?.toString();
    const password = data.get('password')?.toString();

    // バリデーション
    if (!email?.includes('@')) {
      return fail(400, {
        email,  // 入力値を返す
        error: 'メールアドレスが無効です'
      });
    }

    if (password && password.length < 8) {
      return fail(400, {
        email,
        error: 'パスワードは8文字以上'
      });
    }

    // 登録処理...
    return { success: true };
  }
};
プレビュー
fail(400, { error: '...' })
// ステータス400でエラーを返す
// 入力値も一緒に返せる

エラー表示とフォームの復元

form propsでアクセス

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

<form method="POST">
  <label>
    メールアドレス
    <input
      name="email"
      type="email"
      value={form?.email ?? ''}
    >
  </label>

  <label>
    パスワード
    <input name="password" type="password">
  </label>

  {#if form?.error}
    <p class="error">{form.error}</p>
  {/if}

  <button>登録</button>
</form>
プレビュー

メールアドレスが無効です

プログレッシブエンハンスメント

use:enhance

JavaScriptでフォームを強化

svelte
<script>
  import { enhance } from '$app/forms';

  let loading = $state(false);
</script>

<form
  method="POST"
  use:enhance={() => {
    loading = true;

    return async ({ result, update }) => {
      loading = false;

      if (result.type === 'success') {
        // 成功時の処理
      }

      await update();  // デフォルトの更新処理
    };
  }}
>
  <button disabled={loading}>
    {loading ? '送信中...' : '送信'}
  </button>
</form>
プレビュー
// use:enhance で
// - ページ遷移なしで送信
// - ローディング状態の管理
// - 成功/失敗時のカスタム処理

アクション後のリダイレクト

成功後にリダイレクト

redirect関数を使用

typescript
// src/routes/login/+page.server.ts
import { redirect, fail } from '@sveltejs/kit';

export const actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    const user = await login(email, password);

    if (!user) {
      return fail(401, {
        email,
        error: 'メールアドレスまたはパスワードが違います'
      });
    }

    // セッションを設定
    cookies.set('session', user.sessionId, { path: '/' });

    // ダッシュボードへリダイレクト
    redirect(303, '/dashboard');
  }
};
プレビュー
// 成功 → redirect()
// 失敗 → fail() でエラーを返す