データの変更
前の章では、URL検索パラメータとNext.js APIを使用して検索とページネーションを実装しました。請求書の作成、更新、削除機能を追加して、Invoicesページの作業を続けましょう!
サーバーアクションとは?
Reactサーバーアクションを使用すると、サーバー上で直接非同期コードを実行できます。これにより、データを変更するためのAPIエンドポイントを作成する必要がなくなります。代わりに、サーバー上で実行され、クライアントまたはサーバーコンポーネントから呼び出せる非同期関数を記述します。
セキュリティはWebアプリケーションの最優先事項であり、さまざまな脅威に対して脆弱になる可能性があります。ここでサーバーアクションが役立ちます。暗号化されたクロージャ、厳密な入力チェック、エラーメッセージのハッシュ化、ホスト制限など、アプリケーションのセキュリティを大幅に向上させる機能が含まれています。
サーバーアクションとフォームの使用
Reactでは、<form>
要素のaction
属性を使用してアクションを呼び出すことができます。アクションは自動的にキャプチャされたデータを含むネイティブのFormDataオブジェクトを受け取ります。
例:
サーバーコンポーネント内でサーバーアクションを呼び出す利点は、プログレッシブエンハンスメントです。クライアントでJavaScriptがまだ読み込まれていない場合でもフォームが機能します。例えば、インターネット接続が遅い場合などです。
Next.jsとサーバーアクション
サーバーアクションはNext.jsのキャッシュとも深く統合されています。サーバーアクションを通じてフォームが送信されると、データを変更するだけでなく、revalidatePath
やrevalidateTag
などのAPIを使用して関連するキャッシュを再検証することもできます。
どのように連携するか見てみましょう!
請求書の作成
新しい請求書を作成する手順は次のとおりです:
- ユーザーの入力をキャプチャするフォームを作成します。
- サーバーアクションを作成し、フォームから呼び出します。
- サーバーアクション内で、
formData
オブジェクトからデータを抽出します。 - データを検証し、データベースに挿入する準備をします。
- データを挿入し、エラーを処理します。
- キャッシュを再検証し、ユーザーを請求書ページにリダイレクトします。
1. 新しいルートとフォームの作成
開始するには、/invoices
フォルダ内に/create
という新しいルートセグメントを追加し、page.tsx
ファイルを作成します:

このルートを使用して新しい請求書を作成します。page.tsx
ファイルに次のコードを貼り付け、時間をかけて調べてください:
このページはcustomers
を取得し、<Form>
コンポーネントに渡すサーバーコンポーネントです。時間を節約するために、<Form>
コンポーネントはすでに作成されています。
<Form>
コンポーネントに移動すると、フォームに次の要素があることがわかります:
- 顧客のリストを含む1つの
<select>
(ドロップダウン)要素。 - 金額用の
type="number"
を持つ1つの<input>
要素。 - ステータス用の
type="radio"
を持つ2つの<input>
要素。 type="submit"
を持つ1つのボタン。
http://localhost:3000/dashboard/invoices/createにアクセスすると、次のUIが表示されます:

2. サーバーアクションの作成
次に、フォームが送信されたときに呼び出されるサーバーアクションを作成しましょう。
lib/
ディレクトリに移動し、actions.ts
という新しいファイルを作成します。このファイルの先頭にReactのuse server
ディレクティブを追加します:
'use server'
を追加することで、ファイル内のすべてのエクスポートされた関数をサーバーアクションとしてマークします。これらのサーバー関数は、クライアントおよびサーバーコンポーネントでインポートして使用できます。このファイル内で使用されていない関数は、最終的なアプリケーションバンドルから自動的に削除されます。
アクション内に"use server"
を追加することで、サーバーコンポーネント内に直接サーバーアクションを記述することもできます。ただし、このコースでは、すべてを別のファイルに整理しておきます。アクション用に別のファイルを作成することをお勧めします。
actions.ts
ファイルに、formData
を受け入れる新しい非同期関数を作成します:
次に、<Form>
コンポーネントで、actions.ts
ファイルからcreateInvoice
をインポートします。<form>
要素にaction
属性を追加し、createInvoice
アクションを呼び出します。
知っておくと良いこと:HTMLでは、
action
属性にURLを渡します。このURLは、フォームデータを送信する先(通常はAPIエンドポイント)です。しかし、Reactでは、
action
属性は特別なプロップと見なされます。つまり、Reactはその上に構築してアクションを呼び出すことを可能にします。内部的には、サーバーアクションは
POST
APIエンドポイントを作成します。これが、サーバーアクションを使用するときに手動でAPIエンドポイントを作成する必要がない理由です。
3. formData
からデータを抽出
actions.ts
ファイルに戻り、formData
の値を抽出する必要があります。使用できるいくつかの方法があります。この例では、.get(name)
メソッドを使用します。
ヒント:多くのフィールドを持つフォームを扱う場合は、JavaScriptの
Object.fromEntries()
とentries()
メソッドの使用を検討してください。
すべてが正しく接続されていることを確認するために、フォームを試してください。送信後、フォームに入力したデータがターミナル(ブラウザではありません)にログとして表示されるはずです。
データがオブジェクトの形になったので、作業がずっと簡単になります。
4. データの検証と準備
フォームデータをデータベースに送信する前に、正しい形式と型であることを確認する必要があります。コースの前半で覚えているかもしれませんが、請求書テーブルは次の形式のデータを期待しています:
今のところ、フォームからはcustomer_id
、amount
、status
しか取得していません。
型の検証と強制
フォームからのデータがデータベースで期待される型と一致していることを検証することが重要です。例えば、アクション内にconsole.log
を追加すると:
amount
がnumber
ではなくstring
型であることに気付くでしょう。これは、type="number"
を持つinput
要素が実際には数値ではなく文字列を返すためです!
型検証を処理するには、いくつかのオプションがあります。手動で型を検証することもできますが、型検証ライブラリを使用すると時間と労力を節約できます。この例では、TypeScriptファーストの検証ライブラリであるZodを使用します。
actions.ts
ファイルでZodをインポートし、フォームオブジェクトの形状に一致するスキーマを定義します。このスキーマは、データベースに保存する前にformData
を検証します。
amount
フィールドは、型を検証しながら文字列から数値に強制的に変更(coerce)するように特別に設定されています。
次に、rawFormData
をCreateInvoice
に渡して型を検証できます:
セント単位での値の保存
JavaScriptの浮動小数点エラーを排除し、より高い精度を確保するために、通常はデータベースに通貨値をセント単位で保存するのが良い方法です。
金額をセントに変換しましょう:
新しい日付の作成
最後に、請求書の作成日として「YYYY-MM-DD」形式の新しい日付を作成します:
5. データをデータベースに挿入
データベースに必要なすべての値が揃ったので、新しい請求書をデータベースに挿入するSQLクエリを作成し、変数を渡すことができます:
現時点では、エラーを処理していません。これについては次の章で説明します。今は、次のステップに進みましょう。
6. 再検証とリダイレクト
Next.jsにはクライアントサイドのルーターキャッシュがあり、一定時間ルートセグメントをユーザーのブラウザに保存します。プリフェッチと組み合わせることで、サーバーへのリクエスト数を減らしながら、ユーザーがルート間を素早く移動できるようになります。
請求書ルートに表示されるデータを更新するため、このキャッシュをクリアしてサーバーへの新しいリクエストをトリガーしたい場合があります。Next.jsのrevalidatePath
関数を使用してこれを行えます:
データベースが更新されると、/dashboard/invoices
パスが再検証され、サーバーから新しいデータが取得されます。
この時点で、ユーザーを/dashboard/invoices
ページにリダイレクトさせたい場合もあります。Next.jsのredirect
関数を使用してこれを行えます:
おめでとうございます!最初のサーバーアクションを実装しました。新しい請求書を追加してテストしてみてください。すべてが正しく動作していれば:
- 送信時に
/dashboard/invoices
ルートにリダイレクトされるはずです - テーブルの上部に新しい請求書が表示されるはずです
請求書の更新
請求書更新フォームは請求書作成フォームと似ていますが、データベースのレコードを更新するために請求書id
を渡す必要があります。請求書id
を取得して渡す方法を見てみましょう。
請求書を更新する手順は次のとおりです:
- 請求書
id
で新しい動的ルートセグメントを作成 - ページパラメータから請求書
id
を読み取る - データベースから特定の請求書を取得
- フォームに請求書データを事前入力
- データベースの請求書データを更新
1. 請求書id
で動的ルートセグメントを作成
Next.jsでは、正確なセグメント名がわからず、データに基づいてルートを作成したい場合に動的ルートセグメントを作成できます。これはブログ投稿のタイトルや商品ページなどに適しています。フォルダ名を角括弧で囲むことで動的ルートセグメントを作成できます。例: [id]
, [post]
, [slug]
/invoices
フォルダ内に[id]
という新しい動的ルートを作成し、その中にedit
ルートとpage.tsx
ファイルを作成します。ファイル構造は次のようになります:
![[id]フォルダがネストされたinvoicesフォルダと、その中のeditフォルダ](https://h8DxKfmAPhn8O0p3.public.blob.vercel-storage.com/learn/light/edit-invoice-route.png)
<Table>
コンポーネントでは、テーブルレコードから請求書のid
を受け取る<UpdateInvoice />
ボタンがあることに注目してください。
<UpdateInvoice />
コンポーネントに移動し、Link
のhref
を更新してid
プロップを受け入れます。テンプレートリテラルを使用して動的ルートセグメントにリンクできます:
2. ページparams
から請求書id
を読み取る
<Page>
コンポーネントに次のコードを貼り付けます:
/create
請求書ページと似ていますが、異なるフォーム(edit-form.tsx
ファイルから)をインポートしていることに注目してください。このフォームには、顧客名、請求金額、ステータスのdefaultValue
が事前入力されている必要があります。フォームフィールドに事前入力するには、id
を使用して特定の請求書を取得する必要があります。
searchParams
に加えて、ページコンポーネントはid
にアクセスするために使用できるparams
プロップも受け入れます。<Page>
コンポーネントを更新してこのプロップを受け取ります:
3. 特定の請求書を取得
次に:
fetchInvoiceById
という新しい関数をインポートし、id
を引数として渡します- ドロップダウンの顧客名を取得するために
fetchCustomers
をインポートします
Promise.all
を使用して請求書と顧客を並列で取得できます:
invoice
が潜在的にundefined
である可能性があるため、ターミナルでinvoice
プロップに関する一時的なTypeScriptエラーが表示されます。今のところ心配する必要はありません。次の章でエラー処理を追加する際に解決します。
素晴らしい!では、すべてが正しく接続されているかテストしてみましょう。http://localhost:3000/dashboard/invoicesにアクセスし、編集する請求書の鉛筆アイコンをクリックします。ナビゲーション後、請求書の詳細が事前入力されたフォームが表示されるはずです:

URLもid
付きで更新されるはずです: http://localhost:3000/dashboard/invoice/uuid/edit
UUID vs 自動増分キー
自動増分キー(例: 1, 2, 3など)の代わりにUUIDを使用しています。これによりURLは長くなりますが、UUIDはID衝突のリスクを排除し、グローバルに一意で、列挙攻撃のリスクを減らすため、大規模なデータベースに最適です。
ただし、よりクリーンなURLを好む場合は、自動増分キーを使用することを好むかもしれません。
4. サーバーアクションにid
を渡す
最後に、データベースで正しいレコードを更新できるように、id
をサーバーアクションに渡したいと思います。次のようにid
を引数として渡すことはできません:
代わりに、JSのbind
を使用してサーバーアクションにid
を渡せます。これにより、サーバーアクションに渡される値がエンコードされます。
注: フォーム内に隠し入力フィールドを使用することも可能です(例:
<input type="hidden" name="id" value={invoice.id} />
)。ただし、値はHTMLソースに平文で表示されるため、機密データには理想的ではありません。
次に、actions.ts
ファイルで、新しいアクションupdateInvoice
を作成します:
createInvoice
アクションと同様に、ここでは:
formData
からデータを抽出- Zodで型を検証
- 金額をセントに変換
- SQLクエリに変数を渡す
- クライアントキャッシュをクリアして新しいサーバーリクエストを行うために
revalidatePath
を呼び出し - ユーザーを請求書ページにリダイレクトするために
redirect
を呼び出し
請求書を編集してテストしてみてください。フォームを送信すると、請求書ページにリダイレクトされ、請求書が更新されるはずです。
請求書の削除
サーバーアクションを使用して請求書を削除するには、削除ボタンを<form>
要素でラップし、bind
を使用してサーバーアクションにid
を渡します:
actions.ts
ファイル内に、deleteInvoice
という新しいアクションを作成します。
このアクションは/dashboard/invoices
パスで呼び出されているため、redirect
を呼び出す必要はありません。revalidatePath
を呼び出すと、新しいサーバーリクエストがトリガーされ、テーブルが再レンダリングされます。
さらに学ぶ
この章では、サーバーアクションを使用してデータを変更する方法を学びました。また、Next.jsキャッシュを再検証するrevalidatePath
APIと、ユーザーを新しいページにリダイレクトするredirect
の使用方法も学びました。
さらに学ぶために、サーバーアクションのセキュリティについても読むことができます。