APIルートでフォームを構築する
HTMLフォームはページを再読み込みしたり、別ページへ遷移したりします。代わりにAPIエンドポイントへフォームデータを送信するには、JavaScriptでフォーム送信をインターセプトする必要があります。
このレシピでは、フォームデータをAPIエンドポイントへ送信し、そのデータを処理する方法を説明します。
前提条件
- オンデマンドレンダリング用のアダプターを導入したプロジェクト
- UIフレームワークのインテグレーションをインストールしていること
レシピ
フォームデータを受け取る
POSTのAPIエンドポイント/api/feedbackを作成します。request.formData()で処理します。使用前に必ず値を検証します。この例ではメッセージを含むJSONオブジェクトをクライアントへ返します。
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">要素を含めます。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> ); }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> ); }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> ); }<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> <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リクエストとしてエンドポイントへ送ります。
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> ); }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> ); }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> ); }<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><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:*ディレクティブを使います。--- import FeedbackForm from "../components/FeedbackForm" --- <FeedbackForm client:load />--- import FeedbackForm from "../components/FeedbackForm" --- <FeedbackForm client:load />--- import FeedbackForm from "../components/FeedbackForm" --- <FeedbackForm client:load />--- import FeedbackForm from "../components/FeedbackForm.svelte" --- <FeedbackForm client:load />--- import FeedbackForm from "../components/FeedbackForm.vue" --- <FeedbackForm client:load />