APIルートでフォームを構築する
HTMLフォームはページを再読み込みしたり、別ページへ遷移したりします。代わりにAPIエンドポイントへフォームデータを送信するには、JavaScriptでフォーム送信をインターセプトする必要があります。
このレシピでは、フォームデータをAPIエンドポイントへ送信し、そのデータを処理する方法を説明します。
- オンデマンドレンダリング用のアダプターを導入したプロジェクト
- UIフレームワークのインテグレーションをインストールしていること
-
フォームデータを受け取る
POST
のAPIエンドポイント/api/feedback
を作成します。request.formData()
で処理します。使用前に必ず値を検証します。この例ではメッセージを含むJSONオブジェクトをクライアントへ返します。
src/pages/api/feedback.ts export const prerender = false; // 「server」モードでは不要ですimport type { APIRoute } from "astro";export const POST: APIRoute = async ({ request }) => {const data = await request.formData();const name = data.get("name");const email = data.get("email");const message = data.get("message");// データのバリデーション — 実際にはこれ以上の検証を行う必要がある場合が多いですif (!name || !email || !message) {return new Response(JSON.stringify({message: "Missing required fields",}),{ status: 400 });}// データを処理して、成功レスポンスを返すreturn new Response(JSON.stringify({message: "Success!"}),{ status: 200 });}; -
使用中のUIフレームワークでフォームコンポーネントを作成します。各入力には、その値の意味を表す
name
属性を付けます。必ず送信用の
<button>
または<input type="submit">
要素を含めます。src/components/FeedbackForm.tsx export default function Form() {return (<form><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button></form>);}src/components/FeedbackForm.tsx export default function Form() {return (<form><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button></form>);}src/components/FeedbackForm.tsx export default function Form() {return (<form><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button></form>);}src/components/FeedbackForm.svelte <form><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button></form>src/components/FeedbackForm.vue <template><form><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button></form></template> -
送信イベントを受け取る関数を作成し、フォームの
submit
ハンドラーとして渡します。関数内では次を行います。
-
ブラウザーの既定の送信処理を上書きするために
preventDefault()
を呼びます。 -
FormData
オブジェクトを作成し、fetch()
でPOST
リクエストとしてエンドポイントへ送ります。
src/components/FeedbackForm.tsx import { useState } from "preact/hooks";export default function Form() {const [responseMessage, setResponseMessage] = useState("");async function submit(e: SubmitEvent) {e.preventDefault();const formData = new FormData(e.target as HTMLFormElement);const response = await fetch("/api/feedback", {method: "POST",body: formData,});const data = await response.json();if (data.message) {setResponseMessage(data.message);}}return (<form onSubmit={submit}><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button>{responseMessage && <p>{responseMessage}</p>}</form>);}src/components/FeedbackForm.tsx import { useState } from "react";import type { FormEvent } from "react";export default function Form() {const [responseMessage, setResponseMessage] = useState("");async function submit(e: FormEvent<HTMLFormElement>) {e.preventDefault();const formData = new FormData(e.target as HTMLFormElement);const response = await fetch("/api/feedback", {method: "POST",body: formData,});const data = await response.json();if (data.message) {setResponseMessage(data.message);}}return (<form onSubmit={submit}><label htmlFor="name">Name<input type="text" id="name" name="name" autoComplete="name" required /></label><label htmlFor="email">Email<input type="email" id="email" name="email" autoComplete="email" required /></label><label htmlFor="message">Message<textarea id="message" name="message" autoComplete="off" required /></label><button>Send</button>{responseMessage && <p>{responseMessage}</p>}</form>);}src/components/FeedbackForm.tsx import { createSignal, createResource, Suspense } from "solid-js";async function postFormData(formData: FormData) {const response = await fetch("/api/feedback", {method: "POST",body: formData,});const data = await response.json();return data;}export default function Form() {const [formData, setFormData] = createSignal<FormData>();const [response] = createResource(formData, postFormData);function submit(e: SubmitEvent) {e.preventDefault();setFormData(new FormData(e.target as HTMLFormElement));}return (<form onSubmit={submit}><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button><Suspense>{response() && <p>{response().message}</p>}</Suspense></form>);}src/components/FeedbackForm.svelte <script lang="ts">let responseMessage: string;async function submit(e: SubmitEvent) {e.preventDefault();const formData = new FormData(e.currentTarget as HTMLFormElement);const response = await fetch("/api/feedback", {method: "POST",body: formData,});const data = await response.json();responseMessage = data.message;}</script><form on:submit={submit}><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button>{#if responseMessage}<p>{responseMessage}</p>{/if}</form>src/components/FeedbackForm.vue <script setup lang="ts">import { ref } from "vue";const responseMessage = ref<string>();async function submit(e: Event) {e.preventDefault();const formData = new FormData(e.currentTarget as HTMLFormElement);const response = await fetch("/api/feedback", {method: "POST",body: formData,});const data = await response.json();responseMessage.value = data.message;}</script><template><form @submit="submit"><label>Name<input type="text" id="name" name="name" required /></label><label>Email<input type="email" id="email" name="email" required /></label><label>Message<textarea id="message" name="message" required /></label><button>Send</button><p v-if="responseMessage">{{ responseMessage }}</p></form></template> -
-
ページで
<FeedbackForm />
コンポーネントをインポートして配置します。必要なタイミングでロジックをハイドレートするため、必ずclient:*
ディレクティブを使います。src/pages/index.astro ---import FeedbackForm from "../components/FeedbackForm"---<FeedbackForm client:load />src/pages/index.astro ---import FeedbackForm from "../components/FeedbackForm"---<FeedbackForm client:load />src/pages/index.astro ---import FeedbackForm from "../components/FeedbackForm"---<FeedbackForm client:load />src/pages/index.astro ---import FeedbackForm from "../components/FeedbackForm.svelte"---<FeedbackForm client:load />src/pages/index.astro ---import FeedbackForm from "../components/FeedbackForm.vue"---<FeedbackForm client:load />