Laravel Inertia.jsでのCRUD処理の理解

Laravel8から導入されたInertia.jsを利用してデータの一覧表示、データ作成、更新、削除をどのように行うのか知りたいという人もいるかと思います。新しい技術にはまだ手を出したくないと尻込みしている人もInertia.jsの基本的なCRUDの処理方法が理解できればInertia.jsを使ったアプリケーション作成にチャレンジしてみようと思うかもしれません。ぜひ本書を利用してInertia.jsのCRUD操作を学んでいただき今後Inertia.jsの技術を取得するべきかどうかの判断に利用してください。
本文書ではLaravel8から新たに追加されたJetStream+Inertia.jsを利用してできるだけシンプルなコードを使ってCRUD処理(Create, Read, Update, Delete)の方法を説明します。CRUD処理の中では、JetStreamに含まれるProfileやTeamのページで実際に利用されるコンポーネントを最大限活用しています。
これからの作業は、Laravel8へのJetStreamはインストール済の状態から開始していくので事前にLaravel8の環境の構築を完了しておいてください。
環境構築
Blog用のモデル・コントローラー作成
動作確認を行うために利用するモデルはtitle, contentの2つの列を持つシンプルなBlog(ブログ)を想定しています。まずBlogのモデル、コントローラーの作成を行います。
Blogモデルの作成はphp artisan make:modelコマンドで行います。実行するとapp¥ModelsにBlog.phpファイルとdatabase¥migrationsの下にマイグレーションファイルが作成されます。
% php artisan make:model Blog -m
Model created successfully.
Created Migration: 2020_11_10_073153_create_blogs_table
次にphp artisan make:controllerコマンドを利用してBlogモデル用のコントローラーを作成します。
% php artisan make:controller BlogController --resource
Controller created successfully.
app¥Http¥Controllerの下にBlogController.phpファイルが作成されます。
Blogテーブルの作成
blogテーブルにはtitleとcontent列のみ作成します。titleはブログのタイトルなのでstring, contentはブログの内容を保存するためtextを設定しています。
先ほどphp artisan mak:modelコマンドで作成したマイグレーションファイルを開いて以下を設定してください。
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
php artisan migrateコマンドを実行してblogsテーブルを作成してください。
% php artisan migrate
Migrating: 2020_11_10_073153_create_blogs_table
Migrated: 2020_11_10_073153_create_blogs_table (5.48ms)
テーブル作成後テーブルへのデータ登録ができるようにBlog.phpファイルに追加した列の情報を追加します。
class Blog extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content'
];
}
ルーティングの追加
/blogsにアクセスするとブログ一覧が表示されるようにルーティングファイルのweb.phpにルーティングを追加します。下記のルーティングの設定ではブラウザから/blogsにアクセスするとBlogController.phpファイルのindexメソッドが実行されます。ルーティングにはnameメソッドで名前blog.indexをつけており、この名前を利用することでこのルーティングにアクセスを行うことができます。
//略
use App\Http\Controllers\BlogController;
//略
Route::get('/blogs', [BlogController::class, 'index'])
->name('blog.index');

php artisan serveコマンドで開発サーバを起動して、/blogsにアクセスしてください。ここまでの設定であればブラウザには真っ白の画面が表示されます。indexメソッドに何も設定を行っていないため何も問題はありません。
アプリケーションのユーザ登録が完了しているユーザのみアクセスできるようにミドルウェアの設定を行います。アクセス制限を行うことができます。
//略
use App\Http\Controllers\BlogController;
//略
Route::get('/blogs', [BlogController::class, 'index'])
->name('blog.index')
->middleware('auth');
ミドルウェアのauthを設定して再度/blogsにアクセスするとログイン画面が表示されます。ユーザの登録が完了していない場合はユーザ登録を行ってください。ユーザの登録・ログインは初期画面の右上から行うことができます。

登録したユーザでログインすると下記の画面が表示されます。

リンクの設定
ログインすると/dashboardにリダイレクトされます。/dashboardで表示されるページの内容はresources¥js¥Pagesの下に保存されているDashboard.vueファイルに記述されています。その理由はweb.phpファイルのdashboardへのルーティングを見ることで確認することができます。
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return Inertia\Inertia::render('Dashboard');
})->name('dashboard');

DashBoard.vueファイルを見るとAppLayout.vueを利用してページのレイアウトが設定されていることがわかります。
<template>
<app-layout> //ここがAppLayout.vue
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Dashboard
</h2>
</template>

AppLayout.vueファイルを開いて、Blogページへのリンクを設定します。routeメソッドに設定しているblog.indexはルーティングで設定したnameの値です。
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<jet-nav-link :href="route('dashboard')" :active="route().current('dashboard')">
Dashboard
</jet-nav-link>
//以下を追加
<jet-nav-link :href="route('blog.index')" :active="route().current('blog.index')">
Blog
</jet-nav-link>
</div>
Blogページへのリンクを追加して保存してブラウザで再度Dashboardにアクセスしても追加したリンクは反映されません。Inertis.jsはPHPとは異なり、更新後はビルドを行う必要があるのでnpm run watchコマンドを実行しておく必要があります。ビルドが完了して再度アクセスするとBlogのリンクが表示されます。しかしリンクをクリックしてもまだページが作成できていないので何も起こりません。


indexページの作成
blogsにアクセスした場合に表示されるページをBlogController.phpファイルのindexメソッドで指定します。Inertiaのrenderメソッドで表示するページを設定します。resources¥js¥Pagesの下にBlogディレクトリを作成してください。その後DashBoard.vueファイルを複製して保存し、名前をIndex.vueに変更してください。
use Inertia\Inertia; //必要
class BlogController extends Controller
{
public function index(){
return Inertia::render('Blog/Index');
}
作成したIndex.vueファイルを開いてwelcomeコンポーネントを削除し、template #headerにあるDashboardをBlogに変更してください。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Blog
</h2>
</template>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
export default {
components: {
AppLayout,
},
}
</script>
blogsにアクセスすると下記のページが表示されます。

Seedingによるデータの挿入
Index.vueにアクセスした際にブログの一覧を表示させるためにはblogテーブルにデータを登録する必要があります。
Seeding機能を利用することでBlogテーブルに複数のダミーデータを短時間で簡単に挿入することができます。
php artisan make:factoryコマンドを利用してテーブルに挿入するデータの構造を記述したFactoryファイルを作成します。
% php artisan make:factory BlogFactory --model=Blog
Factory created successfully.
database¥factoriesディレクトリにBlogFactory.phpファイルが作成されるのでtitle, content列に挿入するダミーデータの定義を行います。
public function definition()
{
return [
'title' => $this->faker->sentence,
'content' => $this->faker->text,
];
}
次にdatabase¥seeders下にあるDatabaseSeeder.phpファイルを開いて下記を設定してください。User用の設定が行っているので行を複製してUserをBlogに変更してください。factoryメソッドの10は10件のデータを作成することを表しています。
public function run()
{
// \App\Models\User::factory(10)->create();
\App\Models\Blog::factory(10)->create();
}
これでSeeding機能の設定は完了です。最後に実際にデータを挿入するphp artisan db:seedコマンドを実行します。successfullyのメッセージが表示されればデータの挿入は完了しています。
% php artisan db:seed
Database seeding completed successfully.
これでInertia.jsを利用してCRUDの動作確認を行う準備は完了です。ここからCRUDの設定を行っていきます。
Blogデータの一覧表示
Seederを利用して挿入したデータをBlogController.php、Index.vueファイルを利用してブラウザに表示します。
BlogController.phpのindexメソッド内でBlogモデルを利用して保存された全データを取得します。データが保存されているかどうかddを利用して確認することができます。
//略
use Inertia\Inertia;
use App\Models\Blog;
class BlogController extends Controller
{
public function index(){
dd(Blog::all());
return Inertia::render('Blog/Index',['blogs' => Blog::all()]);
}
//略
blogsにアクセスして10件のデータが保存されているか確認してください。

データがテーブルに保存されていることが確認できたら実際にIndex.vueファイルを利用してデータを表示させてみましょう。
Index.vueファイルではBlogController.phpから渡された変数blogsのデータを受け取るためにpropsを利用します。propsで取得したデータはv-forを利用して展開します。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Blog
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<table>
<thead>
<tr>
<th>タイトル</th>
<th>コンテンツ</th>
</tr>
</thead>
<tbody>
<tr v-for="blog in blogs" :key="blog.id">
<td class="border px-4 py-2">{{ blog.title }}</td>
<td class="border px-4 py-2">{{ blog.content }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
export default {
props:['blogs'],
components: {
AppLayout,
},
}
</script>

ブラウザで確認するとテーブルに保存されている10件分のBlogデータが一覧表示されます。

Inertia.jsを利用した場合のデータの一覧表示の方法を確認することができました。
Blogデータの作成(create)
先ほどはSeederを利用してデータの挿入を行いましたがここからは入力フォームを利用してBlogデータの作成を行います。
ルーティングの追加
Blogデータの入力フォームページを表示するためのルーティングをweb.phpファイルに新たに追加します。今後更新、削除を行うためのルーティングが必要となるのでそれらのルーティングも一緒に設定を行っておきます。
Route::resource('/blogs', BlogController::class)
->names(['index'=>'blog.index',
'create' => 'blog.create',
'edit' => 'blog.edit',
'update' => 'blog.update',
'destroy' => 'blog.destroy',
'store'=>'blog.store'])
->middleware(['auth']);
リンクボタンの作成
Index.vueファイルに”作成”ボタンを追加し、ボタンをクリックすると作成ページに移動できるようにリンクの設定を行います。
ボタンにはLaravelのJetStreamが持つボタンコンポーネントを利用します。JetStreamで利用されているコンポーネントはresource¥js¥JetStreamの中に保存されています。ボタンのコンポーネントファイルはButtom.vueファイルです。

Index.vueのtableタグの上にボタンコンポーネントのタグを追加しています。ボタンコンポーネントはJetButtonという名前でimportを行っており、templateタグ内で利用する場合はjet-buttonタグで記述します。
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<div>
<inertia-link :href="route('blog.create')">
<jet-button class="bg-blue-700 text-base">Blogを作成</jet-button>
</inertia-link>
</div>
<table>
//略
import AppLayout from "@/Layouts/AppLayout";
import JetButton from "@/Jetstream/Button";
export default {
props: ["locations"],
components: {
AppLayout,
JetButton,
},
テーブルの上に”作成”ボタンが表示されますがリンク先のページを設定していないのでクリックしても作成ページがブラウザ上に表示されることはありません。

Createページの作成
Blogの入力フォームを含むCreateページの作成を行います。BlogController.phpファイルのcreateメソッド内で入力フォームを記述するBlog¥Create.vueファイルを指定します。
public function create()
{
return Inertia::render('Blog/Create');
}
resources¥js¥Pages¥BlogにあるIndex.vueを複製してCreate.vueを作成します。テーブルなど必要ない情報は削除します。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Blog
</h2>
</template>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
export default {
props:['blogs'],
components: {
AppLayout,
},
}
</script>
ブラウザで確認すると以下のように表示されます。

入力フォームの追加
FormSectionコンポーネント
template #headerの閉じタグの下に入力フォームを追加しますが、入力フォームにはJetStreamのFormSectionコンポーネントを利用します。
FormSectionコンポーネントはtitle, description, form, actionsの4つの名前付きSlotがあります。
まずはtitle, descriptionスロットのみを設定しブラウザ上に表示します。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Blog
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<jet-form-section>
<template #title>Blog作成</template>
<template #description>Blogの追加を行います</template>
</jet-form-section>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
import JetFormSection from "@/Jetstream/FormSection";
export default {
props:['blogs'],
components: {
AppLayout,
JetFormSection,
},
}
</script>
template #titleタグの中に記述した内容は、FormSection.vueファイルのslotタグのname属性にtitleを設定している場所に挿入されます。
<slot name="title"></slot>

ブラウザで確認すると設定したtitleとdescriptionが画面の左側に表示されます。

次はFormSectionコンポーネントのformスロットにBlogデータのtitleに関するlabel要素とinput要素を追加しますが、JetStreamのコンポーネントにはlabel用のLabel.vue, input用のInput.vueがあるのでそれを利用します。
template #descriptionの閉じタグの下にtemplate #formタグを追加します。
<template #form>
<div class="col-span-6 sm:col-span-4">
<jet-label for="title" value="タイトル" />
<jet-input
id="title"
type="text"
class="mt-1 block w-full"
v-model="form.title"
/>
</div>
</template>
scriptタグの中でInput.vueとLabel.vueをimportしてそれぞれの名前をJetInputとJetLabelとしています。templateタグで利用する場合は<jet-input>、<jet-label>と記述して利用します。
formプロパティ
jet-inputタグの中のv-modelでデータバインディングを行っているformプロパティはVue.jsの設定で下記のように設定します。
<script>
import AppLayout from '@/Layouts/AppLayout'
import JetFormSection from "@/Jetstream/FormSection";
import JetInput from "@/Jetstream/Input";
import JetLabel from "@/Jetstream/Label";
export default {
components: {
AppLayout,
JetFormSection,
JetInput,
JetLabel
},
data() {
return {
form: this.$inertia.form(
{
_method: "POST",
title: "",
description: "",
},
{
bag: "blogCreate",
}
)
};
}
}
</script>
formプロパティについてはほとんどの人は見慣れない書式だと思います。Laravel Jetsteamの公式ドキュメントに解説が記載されており、そのドキュメンを参考に設定を行っています。

フォームとバリデーションエラーを効率的に処理するためにlaravel-jetstreamというパッケージがJetStreamと一緒にインストールされています。laravel-jetsteamパッケージをインストールすることでデータプロパティのform内の処理はInertia.jsの単独の機能ではなくLaravel Jetstream用に機能追加が行われています。
$inertiaオブジェクトでformメソッドを追加し、引数にdata、optionsを設定することで新たにinertiaFormオブジェクトを作成します。
this.$inertia.form(data,options)
formの引数のdataにはメソッドの設定とルーティングに送信するデータプロパティを設定します。Create.vueではBlogデータを新規で作成を行うのでルーティングに対してPOSTメソッドを実行するため、_methodプロパティにPOSTを設定しています。送信するデータはtitleとcontentなのでtitleプロパティとcontentプロパティを設定しています。
form: this.$inertia.form(
{
_method: "POST",
title: "",
content: "",
},
formの引数のoptionsにはbagが設定されています。
form: this.$inertia.form(
{
_method: "POST",
title: "",
description: "",
},
{
bag: "blogCreate", //ここ
}
)
bagに設定している’blogCreate’はバリデーションでエラーが発生時にエラーを識別し、表示する際に利用します。後ほどコントローラーのバリデーションを設定する際に同じ名前を設定する必要があります。
入力フォーム
ここまでの設定で再度ページを確認するとタイトルラベルのinput要素が表示されていることがわかります。

コンテントについてはJetStreamにtextareaのコンポーネントが存在しないので今回はコンポーネントを利用せずに直接textareaを設定します。
<template #form>
<div class="col-span-6 sm:col-span-4">
<jet-label for="title" value="タイトル" />
<jet-input
id="title"
type="text"
class="mt-1 block w-full"
v-model="form.title"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="content" value="コンテント" />
<textarea v-model="form.content" class="mt-1 block w-full form-input rounded-md shadow-sm"></textarea>
</div>
</template>
追加後ブラウザで確認するとタイトルとコンテンツの入力欄が表示されます。

入力フォームを完成するためには”作成”ボタンが必要となります。ボタンはactionsスロットの中で設定を行います。
ボタンはIndex.vueファイルで”BLOGを作成”ボタンを作成する際に利用したJetStreamのButton.vueを再利用します。
template #form閉じタグの下にtemplate #actionsを追加します。
<template #actions>
<jet-button
class="bg-blue-700"
>
作成
</jet-button>
</template>
ブラウザで確認すると”作成”ボタンが入力フォームの右下に表示されますが作成ボタンをクリックしても何も起こりません。

submittedイベント
FormSectionコンポーネントについてはtitle, description, form, actionsの4つの名前付きSlotがあり、ここまでの流れですべてのSlotを設定しました。
FormSection.vueを開いてさらに内容を確認します。FormSection.vueの中にはformタグも含まれておりフォーム内でボタンをクリックするとsubmitイベントにより$emitが実行され引数に設定されているsubmittedイベントが親コンポーネントに送られるように設定されています。
<template>
<div class="md:grid md:grid-cols-3 md:gap-6">
<jet-section-title>
<template #title><slot name="title"></slot></template>
<template #description><slot name="description"></slot></template>
</jet-section-title>
<div class="mt-5 md:mt-0 md:col-span-2">
<form @submit.prevent="$emit('submitted')"> //ここ
<div class="shadow overflow-hidden sm:rounded-md">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<slot name="form"></slot>
</div>
</div>
FormSection.vueコンポーネントの親コンポーネントであるCreate.vueファイルでsubmittedイベントを受け取る処理を追加します。
jet-form-sectionタグに@submittedを追加し、submittedイベントを受け取ったら、createBlogメソッドを実行するように設定を行います。
<jet-form-section @submitted="createBlog">
submittedイベントを受け取って、createBlogメソッドが実行されるのか動作確認を行うため、createBlogメソッドが実行できたらコンソールにsubmittedを表示させます。
methods:{
createBlog(){
console.log('submitted')
}
}
”作成”ボタンをクリックしてブラウザのデベロッパーツールのコンソールに”submitted”が表示されたらFormSectionから送られてきたsubmittedイベントが親コンポーネントで正常に受け取れていることがわかります。
createBlogメソッドの中の処理はconsole.logからブログが登録できるルーティングへのPOSTリクエストを行う処理に変更します。
ルーティングの追加
createBlogメソッド内から送信されるPOSTリクエストの送信先はweb.phpへのルーティングに登録済なので追加作業は必要ありません。
Route::resource('/blogs', BlogController::class)
->names(['index'=>'blog.index',
'create' => 'blog.create',
'edit' => 'blog.edit',
'update' => 'blog.update',
'destroy' => 'blog.destroy',
'store'=>'blog.store']) //このルーティングを利用
->middleware(['auth']);
データの作成処理(store)
BlogController.phpファイルのstoreメソッドにCreate.vueのcreateBlogメソッドから送信されてくるBlogデータを作成する処理を追加します。
入力フォームで入力したデータを取り出し、Blogモデルのcreateメソッドを利用してBlogデータを新規作成しています。Blogデータ作成後はBlogの一覧画面にリダイレクトされます。
//略
use Illuminate\Support\Facades\Redirect;
//略
public function store(Request $request)
{
$input = $request->all();
Blog::create($input);
return Redirect::route('blog.index');
}
コントローラー側のデータ作成処理の追加が完了したらCreate.vueファイル側でPOSTリクエストの処理を追加します。POSTリクエストの処理は、”作成”ボタンをクリックした際に実行されるcreateBlogメソッドの中に追加します。routeメソッドの引数にはルーティングで設定したnameのblog.storeを指定しています。
methods:{
createBlog(){
this.form.post(route("blog.store"));
}
}
設定が完了したので実際にBlog作成ページから入力したデータが保存されるか確認を行います。
Blog作成ページのタイトルとコンテントを入力して”作成”ボタンをクリックします。

クリックするとブログ一覧のページ(Index.vue)へのリダイレクトが行われ入力したBlog情報が表示されることが確認できます。

バリデーションの追加
ここまでのstoreメソッドの設定では、タイトルとコンテンツを空欄に実行するとNot Null制約によりデータは登録できません。

データベースへのSQLの実行時ではなくデータ登録前に送信されてきたデータが正しいものなのかバリデーション機能を利用してチェックを行います。
title, contentのrequiredのバリデーションを設定しています。どちらの項目も入力が必須となります。validateWithBagメソッドでバリデーションを実行しますが、メソッドの引数にblogCreateの文字列を入れています。これがCreate.vueのthis.$inertia.formのオプションで設定したbagの値と一致します。
public function store(Request $request)
{
$input = $request->all();
Validator::make($input, [
'title' => ['required'],
'content' => ['required']
])->validateWithBag('blogCreate');
Blog::create($input);
return Redirect::route('blog.index');
}
バリデーションの追加を行ったのでタイトルだけ入力して”作成”ボタンをクリックします。

実行するとエラーの表示がない上、入力したタイトルも消えてしまいます。

まず入力した値が保持できるようにthis.$inertia.formのオプションにresetOnSucessを追加し、falseを設定します。
form: this.$inertia.form(
{
_method: "POST",
title: "",
content: "",
},
{
bag: "blogCreate",
resetOnSuccess: false,
}
)
設定後再度タイトルのみ入力して”作成”ボタンを押してください。下記のように入力した値が保持されていればresetOnSuccessの設定は反映されています。

バリデーションエラーが表示されない理由はエラーを表示する設定を全く行っていないためです。バリデーションエラーを表示するためのコンポーネントInputError.vueがJetSteamにあるのでそれを利用します。
Create.vueファイルのscriptタグでInputError.vueファイルをimportとしてコンポーネントに登録します。
<script>
import AppLayout from '@/Layouts/AppLayout'
import JetFormSection from "@/Jetstream/FormSection";
import JetInput from "@/Jetstream/Input";
import JetLabel from "@/Jetstream/Label";
import JetButton from "@/Jetstream/Button";
import JetInputError from "@/Jetstream/InputError";
export default {
props:['blogs'],
components: {
AppLayout,
JetFormSection,
JetInput,
JetLabel,
JetButton,
JetInputError,
},
追加したJetInputErrorを各入力項目の下に追加します。form.errorの引数には各項目の名前を入れてください。
<template #form>
<div class="col-span-6 sm:col-span-4">
<jet-label for="title" value="タイトル" />
<jet-input
id="title"
type="text"
class="mt-1 block w-full"
v-model="form.title"
/>
<jet-input-error
:message="form.error('title')"
class="mt-2"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<jet-label for="content" value="コンテント" />
<textarea v-model="form.content" class="mt-1 block w-full form-input rounded-md shadow-sm"></textarea>
<jet-input-error
:message="form.error('content')"
class="mt-2"
/>
</div>
</template>
これでバリデーションエラー表示の設定は完了です。何も入れずに”作成”ボタンを押すとエラーが表示されます。

Blogデータの削除
Blogデータの作成方法が確認できたので作成したデータの削除方法を確認します。Index.vueファイルのブログ一覧の各行に削除ボタンを追加します。
<table class="table-fixed">
<thead>
<tr>
<th class="w-3/12">タイトル</th>
<th class="w-7/12">コンテンツ</th>
<th class="w-2/12">削除</th>
</tr>
</thead>
<tbody>
<tr v-for="blog in blogs" :key="blog.id">
<td class="border px-4 py-2">{{ blog.title }}</td>
<td class="border px-4 py-2">{{ blog.content }}</td>
<td class="border px-4 py-2 text-center">
<jet-button class="bg-red-700 text-base">削除</jet-button>
</td>
</tr>
</tbody>
</table>

ブラウザで確認すると各行に削除ボタンが追加されます。

”削除”ボタンをクリックすると削除処理が実行されるように新たにクリックイベントとdeleteBlogメソッドを追加します。引数には削除する行を識別できるようにblog.idを設定しています。
<jet-button class="bg-red-700 text-base" @click.native="deleteBlog(blog.id)">削除</jet-button>
動作確認のためdeleteBlogメソッドではコンソールにクリックした行のblogのidを表示します。
methods:{
deleteBlog(id){
console.log(id);
}
}
設定後、”削除”ボタンをクリックするとブラウザのデベロッパーツールのコンソールにクリックしたBlogデータのidが表示されます。
ルーティング
Blogデータを削除するためにはweb.phpへ削除処理を実行するルーティングを追加する必要がありますが、すでに設定済なのでルーティングの追加作業はありません。
Route::resource('/blogs', BlogController::class)
->names(['index'=>'blog.index',
'create' => 'blog.create',
'edit' => 'blog.edit',
'update' => 'blog.update',
'destroy' => 'blog.destroy',//ここ
'store'=>'blog.store'])
->middleware(['auth']);
データの削除処理(destroy)
データの削除処理をBlogController.phpファイルのdestroyメソッドに追加します。
Index.vueファイルからDELETEメソッドでBlogのidが送信されてきますが、モデルバインディングによりidを持つ$blogデータが取得できるため、deleteメソッドを利用して削除を行っています。削除後はBlog一覧の画面にリダイレクトされます。
public function destroy(Blog $blog)
{
$blog->delete();
return Redirect::route('blog.index');
}
BlogController.phpファイルで削除処理の追加が完了したら、Index.vueファイル側で削除のPOSTリクエストの追加を行います。
データ作成時と同様にthis.$inertia.formを利用します。今回は削除を行うので_methodにはDELETEを指定します。完了後、”削除”ボタンをクリックすると”削除”ボタンを押した行が削除されます。
export default {
props:['blogs'],
components: {
AppLayout,
JetButton,
},
data() {
return {
form: this.$inertia.form(
{
_method: "DELETE",
}
),
};
},
methods:{
deleteBlog(id){
this.form.post(route("blog.destroy", id), {
preserveScroll: true,
});
}
}
}
preserveScrollがtrueと設定されていますが、これを設定しない場合画面をスクロールして削除ボタンを押すと削除が完了してリダイレクトした際にページの上部からブラウザに表示されます。trueに設定した場合は、リダイレクトした際にページの上部から表示されるのではなく削除ボタンをクリックした時に表示されていた画面が表示されます。
Blogデータの更新処理(edit, update)
データの作成、削除方法が確認できたので次は作成したデータの更新処理を行う方法を確認します。
Index.vueファイルのブログ一覧の各行の”削除”ボタンの横に”更新”ボタンを追加します。
<table class="table-fixed">
<thead>
<tr>
<th class="w-3/12">タイトル</th>
<th class="w-5/12">コンテンツ</th>
<th class="w-2/12">更新</th>
<th class="w-2/12">削除</th>
</tr>
</thead>
<tbody>
<tr v-for="blog in blogs" :key="blog.id">
<td class="border px-4 py-2">{{ blog.title }}</td>
<td class="border px-4 py-2">{{ blog.content }}</td>
<td class="border px-4 py-2 text-center">
<jet-button class="bg-green-500 text-base">更新</jet-button>
</td>
<td class="border px-4 py-2 text-center">
<jet-button class="bg-red-700 text-base" @click.native="deleteBlog(blog.id)">削除</jet-button>
</td>
</tr>
</tbody>
</table>
ブラウザで確認すると更新ボタンが表示されます。

更新ボタンをクリックすると更新ページに移動する必要があるため、更新ボタンにリンクを設定します。
<inertia-link :href="route('blog.edit', blog.id)">
<jet-button class="bg-green-500 text-base">更新</jet-button>
</inertia-link>
blog.editのルーティングは設定済ですがBlogController.phpファイルにeditメソッドが何も記述されていないため更新ボタンをクリックしても移動することはできません。
Edit.vueファイルの作成
更新ボタンをクリックするとBlogの更新ページを表示する必要があります。更新ページはIndex.vueファイルを複製してEdit.vueファイルとします。
Edit.vueファイルが作成できたら、BlogController.phpファイルのeditメソッドからEdit.vueファイルを表示できるように下記の設定を行います。更新を行いたいBlogデータは更新ボタンのリンクで渡されたidとモデルバインディングを利用して取得しています。また取得したデータ$blogをEdit.vueに渡しています。
public function edit(Blog $blog){
return Inertia::render('Blog/Edit',['blog' => $blog]);
}
Edit.vueファイルを開いてtitleとdescriptionを変更します。
<template #title>Blog更新</template>
<template #description>Blogの更新を行います</template>
変更後、ブログ一覧にある”更新”ボタンをクリックし下記の画面が表示されればここまでの設定に問題はありません。

propsの設定
変数を$blogが渡されていますが、タイトル、コンテンツには何も表示されていません。コントローラーから渡されたデータはvueファイルではpropsを使って取得します。
propsで取得したblogはthis.$inertia.formのデータプロパティの初期値として設定を行います。また_methodは更新なのでPUTを設定しています。
export default {
props:['blog'],
components: {
//略
},
data() {
return {
form: this.$inertia.form(
{
_method: "PUT",
title: this.blog.title,
content: this.blog.content,
},
{
bag: "blogUpdate",
resetOnSuccess: false,
}
)
}
},
この設定が完了後、再度ブラウザで確認するとタイトルとコンテンツに既存のデータが表示されます。

update処理
”作成”ボタンの名前を”更新”ボタンに変更します。
<template #actions>
<jet-button
class="bg-blue-700"
>
更新
</jet-button>
</template>
更新ボタンをクリックすると作成と同様にFormSection.vueからsubmittedイベントが送られてきました。submittedイベントを受け取った後に実行するメソッドをupdateBlogに変更します。変更はjet-form-sectionタグで行います。
<jet-form-section @submitted="editBlog">
editBlogメソッドで実行しているpostリクエストの送信先もblog.storeからblog.updateに変更します。
methods:{
editBlog(){
this.form.post(route("blog.update",this.blog.id));
}
}
blog.updateのルーティングは設定済ですが、BlogCotroller.phpのupdateメソッドでは何も行っていないのでブラウザ上で更新ボタンをクリックしても何も起こりません。
BlogController.phpのupdateメソッドで更新処理を追加します。先ほど設定したstoreメソッドとほぼ同じ内容です。validateWithBagメソッドの引数のblogUpdateはEdit.vueファイルのthis.$inertia.formのbagオプションの値と同じにする必要があります。
public function update(Request $request, Blog $blog)
{
$input = $request->all();
Validator::make($input, [
'title' => ['required'],
'content' => ['required']
])->validateWithBag('blogUpdate');
$blog->update($input);
return Redirect::route('blog.index');
}
設定後、更新が行えることを確認してください。更新が行えることが確認できたら、タイトルからコンテンツの値を削除して空にして、エラーメッセージが表示されるのか確認を行ってください。

更新ができ、エラーメッセージが表示されれば更新機能の設定は完了です。
ここまでの設定が完了できればInertia.jsでCRUDを実装するためにはどのような処理が必要になるのか理解できたかと思います。