💁

Laravel 11 CRUD Hands-On

2024/08/17に公開

CRUD

お盆に実家に帰って特にやることもなかったのでLaravelでCRUDをやってみました。

https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

ググりながらやったのですがLaravelのバージョンが古かったりLaravel Sailの使用が前提だったりLaravelやArtisanの機能をフルに使っていないものばかりでしたので自分なりのやり方を残しておくことにします。

開発環境はGitpodです。

https://gitpod.new/

Laravel 11のDBでSQLiteがデフォルトで使用されるようになりましたので、別にGitpodを使わなくても、PHPとComposerがインストールされていてLaravelに必要なPHP拡張機能が有効になっていれば、様々なOSで簡単に開発を始めることができます。
例えばGlitchでもできますので、以下の記事を参考にして試してみてください。

https://zenn.dev/tkithrta/articles/1d50c5e469fdbe

$ php -v
PHP 8.3.8 (cli) (built: Jun  8 2024 21:34:22) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.8, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.8, Copyright (c), by Zend Technologies
$ composer -V
Composer version 2.7.7 2024-06-10 22:11:12
PHP version 8.3.8 (/usr/bin/php8.3)
Run the "diagnose" command to get more detailed diagnostics output.
$ php artisan -V
Laravel Framework 11.20.0

Create Project

まずはComposerでプロジェクトを作成します。

$ composer create-project laravel/laravel example-app
Creating a "laravel/laravel" project at "./example-app"
Installing laravel/laravel (v11.1.4)

...

   INFO  Discovering packages.

...

   INFO  No publishable resources for tag [laravel-assets].

...

> @php artisan key:generate --ansi

   INFO  Application key set successfully.

> @php -r "file_exists('database/database.sqlite') || touch('database/database.sqlite');"
> @php artisan migrate --graceful --ansi

   INFO  Preparing database.

  Creating migration table ............................................................................................................. 9.82ms DONE

   INFO  Running migrations.

  0001_01_01_000000_create_users_table ................................................................................................. 9.37ms DONE
  0001_01_01_000001_create_cache_table ................................................................................................. 2.88ms DONE
  0001_01_01_000002_create_jobs_table .................................................................................................. 7.62ms DONE

プロジェクトの作成が終わったらカレントディレクトリを移動します。

$ cd example-app

続いてartisanコマンドでPostモデルを作ります。この時--all-aオプションをつけるとモデルに紐づいたMigration, Seeder, Factory, Policy, Resource Controller, Form Requestも全部作ってくれます。
今回はPolicy以外全部使います。

$ php artisan make:model Post -a

   INFO  Model [app/Models/Post.php] created successfully.

   INFO  Factory [database/factories/PostFactory.php] created successfully.

   INFO  Migration [database/migrations/2024_08_17_120000_create_posts_table.php] created successfully.

   INFO  Seeder [database/seeders/PostSeeder.php] created successfully.

   INFO  Request [app/Http/Requests/StorePostRequest.php] created successfully.

   INFO  Request [app/Http/Requests/UpdatePostRequest.php] created successfully.

   INFO  Controller [app/Http/Controllers/PostController.php] created successfully.

   INFO  Policy [app/Policies/PostPolicy.php] created successfully.

またViewは作ってくれないので別途作成します。

$ php artisan make:view posts/index

   INFO  View [resources/views/posts/index.blade.php] created successfully.

$ php artisan make:view posts/create

   INFO  View [resources/views/posts/create.blade.php] created successfully.

$ php artisan make:view posts/show

   INFO  View [resources/views/posts/show.blade.php] created successfully.

$ php artisan make:view posts/edit

   INFO  View [resources/views/posts/edit.blade.php] created successfully.

これでMVCで使う初期ファイルがすべて完成しました。

Model

初めにデータベースのマイグレーションファイルにcontentカラムを追加します。

diff -ur -x composer.lock -x vendor example-app-d/database/migrations/2024_08_17_120000_create_posts_table.php example-app/database/migrations/2024_08_17_120000_create_posts_table.php
--- example-app-d/database/migrations/2024_08_17_120000_create_posts_table.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/migrations/2024_08_17_120000_create_posts_table.php    2024-08-17 12:00:00.000000000 +0000
@@ -13,6 +13,7 @@
     {
         Schema::create('posts', function (Blueprint $table) {
             $table->id();
+            $table->text('content');
             $table->timestamps();
         });
     }

ファクトリーファイルにFaker機能でコンテンツを自動生成できるようにします。

diff -ur -x composer.lock -x vendor example-app-d/database/factories/PostFactory.php example-app/database/factories/PostFactory.php
--- example-app-d/database/factories/PostFactory.php    2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/factories/PostFactory.php      2024-08-17 12:00:00.000000000 +0000
@@ -17,7 +17,7 @@
     public function definition(): array
     {
         return [
-            //
+            'content' => fake()->paragraph(),
         ];
     }
 }

シーダーファイルからFactoryを呼び出します。
とりあえず20回。
useでPostモデルも一緒に呼び出しましょう。

diff -ur -x composer.lock -x vendor example-app-d/database/seeders/PostSeeder.php example-app/database/seeders/PostSeeder.php
--- example-app-d/database/seeders/PostSeeder.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/seeders/PostSeeder.php 2024-08-17 12:00:00.000000000 +0000
@@ -4,6 +4,7 @@

 use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
+use App\Models\Post;

 class PostSeeder extends Seeder
 {
@@ -12,6 +13,6 @@
      */
     public function run(): void
     {
-        //
+        Post::factory()->count(20)->create();
     }
 }

このままだといちいちPostSeederを指定しないといけなくなるのでDatabaseSeederもいじります。
元々あったUserモデルに関するFactoryは今回使わないのでコメントアウトしておきましょう。

diff -ur -x composer.lock -x vendor example-app-d/database/seeders/DatabaseSeeder.php example-app/database/seeders/DatabaseSeeder.php
--- example-app-d/database/seeders/DatabaseSeeder.php   2024-07-16 14:39:20.000000000 +0000
+++ example-app/database/seeders/DatabaseSeeder.php     2024-08-17 12:00:00.000000000 +0000
@@ -13,11 +13,13 @@
      */
     public function run(): void
     {
+        $this->call(PostSeeder::class);
+
         // User::factory(10)->create();

-        User::factory()->create([
-            'name' => 'Test User',
-            'email' => 'test@example.com',
-        ]);
+        // User::factory()->create([
+        //     'name' => 'Test User',
+        //     'email' => 'test@example.com',
+        // ]);
     }
 }

最後にPostモデルでfillableプロパティを指定しましょう。
指定しないとデータベースに書き込む時にエラーが発生することがあります。

diff -ur -x composer.lock -x vendor example-app-d/app/Models/Post.php example-app/app/Models/Post.php
--- example-app-d/app/Models/Post.php   2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Models/Post.php     2024-08-17 12:00:00.000000000 +0000
@@ -8,4 +8,6 @@
 class Post extends Model
 {
     use HasFactory;
+
+    protected $fillable = ['content'];
 }

Postモデルが完成したのでマイグレーションとシーディングをやってみましょう。
この時初回のマイグレーションが残っていると不整合が発生するかもしれないのでmigrate:refreshでartisanコマンドを実行します。

$ php artisan migrate:refresh --seed

...

   INFO  Rolling back migrations.

  0001_01_01_000002_create_jobs_table .................................................................................................. 6.06ms DONE
  0001_01_01_000001_create_cache_table ................................................................................................. 2.87ms DONE
  0001_01_01_000000_create_users_table ................................................................................................. 4.10ms DONE


   INFO  Running migrations.

  0001_01_01_000000_create_users_table ................................................................................................. 7.88ms DONE
  0001_01_01_000001_create_cache_table ................................................................................................. 2.50ms DONE
  0001_01_01_000002_create_jobs_table .................................................................................................. 6.76ms DONE
  2024_08_17_120000_create_posts_table ................................................................................................. 1.66ms DONE


   INFO  Seeding database.

  Database\Seeders\PostSeeder .............................................................................................................. RUNNING
  Database\Seeders\PostSeeder ........................................................................................................... 43 ms DONE

SQLiteのSQLを書いて実際に見てみましょう。

$ php artisan db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .mode line
sqlite> SELECT * FROM posts;
        id = 1
   content = Quod laudantium dolore qui architecto nemo unde inventore. Cupiditate dolor impedit cupiditate est non. Sed et exercitationem nemo facere laboriosam id. Ab voluptatem qui illum.
created_at = 2024-08-17 12:00:00
updated_at = 2024-08-17 12:00:00

...
SCHEMA
CREATE TABLE "posts" (
  "id" integer PRIMARY KEY autoincrement NOT NULL,
  "content" text NOT NULL,
  "created_at" datetime,
  "updated_at" datetime
)

FactoryとSeederによって生成されたダミーデータが追加されていることを確認できました。

Controller

続いてPostモデルに紐づいたリソースコントローラーファイルのPostコントローラーにデータベースに関する処理やページを表示する処理を追加します。

diff -ur -x composer.lock -x vendor example-app-d/app/Http/Controllers/PostController.php example-app/app/Http/Controllers/PostController.php
--- example-app-d/app/Http/Controllers/PostController.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Controllers/PostController.php 2024-08-17 12:00:00.000000000 +0000
@@ -13,7 +13,8 @@
      */
     public function index()
     {
-        //
+        $posts = Post::oldest()->get();
+        return view('posts.index', compact('posts'));
     }

     /**
@@ -21,7 +22,7 @@
      */
     public function create()
     {
-        //
+        return view('posts.create');
     }

     /**
@@ -29,7 +30,8 @@
      */
     public function store(StorePostRequest $request)
     {
-        //
+        Post::create($request->all());
+        return redirect()->route('posts.index')->with('success', 'Create');
     }

     /**
@@ -37,7 +39,7 @@
      */
     public function show(Post $post)
     {
-        //
+        return view('posts.show', compact('post'));
     }

     /**
@@ -45,7 +47,7 @@
      */
     public function edit(Post $post)
     {
-        //
+        return view('posts.edit',compact('post'));
     }

     /**
@@ -53,7 +55,8 @@
      */
     public function update(UpdatePostRequest $request, Post $post)
     {
-        //
+        $post->update($request->all());
+        return redirect()->route('posts.index')->with('success', 'Update');
     }

     /**
@@ -61,6 +64,7 @@
      */
     public function destroy(Post $post)
     {
-        //
+        $post->delete();
+        return redirect()->route('posts.index')->with('success', 'Delete');
     }
 }

更にStorePostRequestファイルとUpdatePostRequestファイルを編集します。
このままだとフォームからデータベースに書き込む際403エラーが表示されてしまうため、authorize()でtrueを返します。
併せてフォームから空や空白のコンテンツを追加できないようにするため、contentカラムにrequiredを追加して必須項目にします。

diff -ur -x composer.lock -x vendor example-app-d/app/Http/Requests/StorePostRequest.php example-app/app/Http/Requests/StorePostRequest.php
--- example-app-d/app/Http/Requests/StorePostRequest.php        2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Requests/StorePostRequest.php  2024-08-17 12:00:00.000000000 +0000
@@ -11,7 +11,7 @@
      */
     public function authorize(): bool
     {
-        return false;
+        return true;
     }

     /**
@@ -22,7 +22,7 @@
     public function rules(): array
     {
         return [
-            //
+            'content' => 'required',
         ];
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/app/Http/Requests/UpdatePostRequest.php example-app/app/Http/Requests/UpdatePostRequest.php
--- example-app-d/app/Http/Requests/UpdatePostRequest.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Requests/UpdatePostRequest.php 2024-08-17 12:00:00.000000000 +0000
@@ -11,7 +11,7 @@
      */
     public function authorize(): bool
     {
-        return false;
+        return true;
     }

     /**
@@ -22,7 +22,7 @@
     public function rules(): array
     {
         return [
-            //
+            'content' => 'required',
         ];
     }
 }

最後にルーティングファイルをしっかり編集してPostコントローラーは完成です。

diff -ur -x composer.lock -x vendor example-app-d/routes/web.php example-app/routes/web.php
--- example-app-d/routes/web.php        2024-07-16 14:39:20.000000000 +0000
+++ example-app/routes/web.php  2024-08-17 12:00:00.000000000 +0000
@@ -1,7 +1,9 @@
 <?php

 use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\PostController;

 Route::get('/', function () {
-    return view('welcome');
+    return '<p><a href="/posts">Post</a></p>';
 });
+Route::resource('posts', PostController::class);

正常に起動できるか確認します。

$ php artisan serve

root

先ほどweb.phpに直接書いたHTMLがルートパスに表示されていることを確認できました。

View

最後にViewのbladeファイルにHTMLを書いていきます。
偉人の名言がコメントアウトされているのですが、今回は省略しています。
index, create, show, editページの順番に編集しています。

diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/index.blade.php example-app/resources/views/posts/index.blade.php
--- example-app-d/resources/views/posts/index.blade.php 2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/index.blade.php   2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,19 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Laravel 11 CRUD</h1>
+@if ($message = Session::get('success'))
+<p>Success: {{ $message }}</p>
+@endif
+<p><a href="{{ route('posts.create') }}">Create</a></p>
+<table>
+  <tr>
+    <th>ID</th>
+    <th>Content</th>
+    <th>Control</th>
+  </tr>
+  @foreach ($posts as $post)
+  <tr>
+    <td>{{ $post->id }}</td>
+    <td>{{ $post->content }}</td>
+    <td><a href="{{ route('posts.show', $post->id) }}">Show</a> | <a href="{{ route('posts.edit', $post->id) }}">Edit</a></td>
+  </tr>
+  @endforeach
+</table>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/create.blade.php example-app/resources/views/posts/create.blade.php
--- example-app-d/resources/views/posts/create.blade.php        2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/create.blade.php  2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,7 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Create</h1>
+<form action="{{ route('posts.store') }}" method="POST">
+  @csrf
+  <input type="text" name="content" autocomplete="off" value="Hello, World!" autofocus />
+  <button>Create</button>
+</form>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/show.blade.php example-app/resources/views/posts/show.blade.php
--- example-app-d/resources/views/posts/show.blade.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/show.blade.php    2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,3 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Show</h1>
+<p>{{ $post->content }}</p>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/edit.blade.php example-app/resources/views/posts/edit.blade.php
--- example-app-d/resources/views/posts/edit.blade.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/edit.blade.php    2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,13 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Edit</h1>
+<form action="{{ route('posts.update', $post->id) }}" method="POST">
+  @csrf
+  @method('PUT')
+  <input type="text" name="content" autocomplete="off" value="{{ $post->content }}" autofocus />
+  <button>Update</button>
+</form>
+<form action="{{ route('posts.destroy', $post->id) }}" method="POST">
+  @csrf
+  @method('DELETE')
+  <button>Delete</button>
+</form>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
INSPIRE
$ php artisan inspire

  “ The only way to do great work is to love what you do. ”
  — Steve Jobs

もしhttpsでサーバーを立ち上げた場合、このままだとFormリクエストを送信した際httpを使用し安全でないフォームで処理されてしまうのでAppServiceProviderでhttpsを強制します。
useでURLに関するファサードを呼び出す必要があります。

diff -ur -x composer.lock -x vendor example-app-d/app/Providers/AppServiceProvider.php example-app/app/Providers/AppServiceProvider.php
--- example-app-d/app/Providers/AppServiceProvider.php  2024-07-16 14:39:20.000000000 +0000
+++ example-app/app/Providers/AppServiceProvider.php    2024-08-17 12:00:00.000000000 +0000
@@ -3,6 +3,7 @@
 namespace App\Providers;

 use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\URL;

 class AppServiceProvider extends ServiceProvider
 {
@@ -19,6 +20,6 @@
      */
     public function boot(): void
     {
-        //
+        URL::forceScheme('https');
     }
 }
PRDUCTION ONLY
-        URL::forceScheme('https');
+        if (app()->isProduction()) URL::forceScheme('https');

最後に起動して動作確認します。

$ php artisan serve

index
create
show
edit
success

終わりです。お疲れ様でした。

Diff

DIFF ALL
$ diff -ur -x composer.lock -x vendor example-app-d example-app
diff -ur -x composer.lock -x vendor example-app-d/app/Http/Controllers/PostController.php example-app/app/Http/Controllers/PostController.php
--- example-app-d/app/Http/Controllers/PostController.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Controllers/PostController.php 2024-08-17 12:00:00.000000000 +0000
@@ -13,7 +13,8 @@
      */
     public function index()
     {
-        //
+        $posts = Post::oldest()->get();
+        return view('posts.index', compact('posts'));
     }

     /**
@@ -21,7 +22,7 @@
      */
     public function create()
     {
-        //
+        return view('posts.create');
     }

     /**
@@ -29,7 +30,8 @@
      */
     public function store(StorePostRequest $request)
     {
-        //
+        Post::create($request->all());
+        return redirect()->route('posts.index')->with('success', 'Create');
     }

     /**
@@ -37,7 +39,7 @@
      */
     public function show(Post $post)
     {
-        //
+        return view('posts.show', compact('post'));
     }

     /**
@@ -45,7 +47,7 @@
      */
     public function edit(Post $post)
     {
-        //
+        return view('posts.edit',compact('post'));
     }

     /**
@@ -53,7 +55,8 @@
      */
     public function update(UpdatePostRequest $request, Post $post)
     {
-        //
+        $post->update($request->all());
+        return redirect()->route('posts.index')->with('success', 'Update');
     }

     /**
@@ -61,6 +64,7 @@
      */
     public function destroy(Post $post)
     {
-        //
+        $post->delete();
+        return redirect()->route('posts.index')->with('success', 'Delete');
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/app/Http/Requests/StorePostRequest.php example-app/app/Http/Requests/StorePostRequest.php
--- example-app-d/app/Http/Requests/StorePostRequest.php        2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Requests/StorePostRequest.php  2024-08-17 12:00:00.000000000 +0000
@@ -11,7 +11,7 @@
      */
     public function authorize(): bool
     {
-        return false;
+        return true;
     }

     /**
@@ -22,7 +22,7 @@
     public function rules(): array
     {
         return [
-            //
+            'content' => 'required',
         ];
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/app/Http/Requests/UpdatePostRequest.php example-app/app/Http/Requests/UpdatePostRequest.php
--- example-app-d/app/Http/Requests/UpdatePostRequest.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Http/Requests/UpdatePostRequest.php 2024-08-17 12:00:00.000000000 +0000
@@ -11,7 +11,7 @@
      */
     public function authorize(): bool
     {
-        return false;
+        return true;
     }

     /**
@@ -22,7 +22,7 @@
     public function rules(): array
     {
         return [
-            //
+            'content' => 'required',
         ];
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/app/Models/Post.php example-app/app/Models/Post.php
--- example-app-d/app/Models/Post.php   2024-08-17 12:00:00.000000000 +0000
+++ example-app/app/Models/Post.php     2024-08-17 12:00:00.000000000 +0000
@@ -8,4 +8,6 @@
 class Post extends Model
 {
     use HasFactory;
+
+    protected $fillable = ['content'];
 }
diff -ur -x composer.lock -x vendor example-app-d/app/Providers/AppServiceProvider.php example-app/app/Providers/AppServiceProvider.php
--- example-app-d/app/Providers/AppServiceProvider.php  2024-07-16 14:39:20.000000000 +0000
+++ example-app/app/Providers/AppServiceProvider.php    2024-08-17 12:00:00.000000000 +0000
@@ -3,6 +3,7 @@
 namespace App\Providers;

 use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\URL;

 class AppServiceProvider extends ServiceProvider
 {
@@ -19,6 +20,6 @@
      */
     public function boot(): void
     {
-        //
+        URL::forceScheme('https');
     }
 }
Binary files example-app-d/database/database.sqlite and example-app/database/database.sqlite differ
diff -ur -x composer.lock -x vendor example-app-d/database/factories/PostFactory.php example-app/database/factories/PostFactory.php
--- example-app-d/database/factories/PostFactory.php    2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/factories/PostFactory.php      2024-08-17 12:00:00.000000000 +0000
@@ -17,7 +17,7 @@
     public function definition(): array
     {
         return [
-            //
+            'content' => fake()->paragraph(),
         ];
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/database/migrations/2024_08_17_120000_create_posts_table.php example-app/database/migrations/2024_08_17_120000_create_posts_table.php
--- example-app-d/database/migrations/2024_08_17_120000_create_posts_table.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/migrations/2024_08_17_120000_create_posts_table.php    2024-08-17 12:00:00.000000000 +0000
@@ -13,6 +13,7 @@
     {
         Schema::create('posts', function (Blueprint $table) {
             $table->id();
+            $table->text('content');
             $table->timestamps();
         });
     }
diff -ur -x composer.lock -x vendor example-app-d/database/seeders/DatabaseSeeder.php example-app/database/seeders/DatabaseSeeder.php
--- example-app-d/database/seeders/DatabaseSeeder.php   2024-07-16 14:39:20.000000000 +0000
+++ example-app/database/seeders/DatabaseSeeder.php     2024-07-16 14:39:20.000000000 +0000
@@ -13,11 +13,13 @@
      */
     public function run(): void
     {
+        $this->call(PostSeeder::class);
+
         // User::factory(10)->create();

-        User::factory()->create([
-            'name' => 'Test User',
-            'email' => 'test@example.com',
-        ]);
+        // User::factory()->create([
+        //     'name' => 'Test User',
+        //     'email' => 'test@example.com',
+        // ]);
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/database/seeders/PostSeeder.php example-app/database/seeders/PostSeeder.php
--- example-app-d/database/seeders/PostSeeder.php       2024-08-17 12:00:00.000000000 +0000
+++ example-app/database/seeders/PostSeeder.php 2024-08-17 12:00:00.000000000 +0000
@@ -4,6 +4,7 @@

 use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
+use App\Models\Post;

 class PostSeeder extends Seeder
 {
@@ -12,6 +13,6 @@
      */
     public function run(): void
     {
-        //
+        Post::factory()->count(20)->create();
     }
 }
diff -ur -x composer.lock -x vendor example-app-d/.env example-app/.env
--- example-app-d/.env  2024-08-17 12:00:00.000000000 +0000
+++ example-app/.env    2024-08-17 12:00:00.000000000 +0000
@@ -1,7 +1,7 @@
 APP_NAME=Laravel
-APP_ENV=local
-APP_KEY=base64:...
-APP_DEBUG=true
+APP_ENV=production
+APP_KEY=base64:...
+APP_DEBUG=false
 APP_TIMEZONE=UTC
 APP_URL=http://localhost
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/create.blade.php example-app/resources/views/posts/create.blade.php
--- example-app-d/resources/views/posts/create.blade.php        2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/create.blade.php  2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,7 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Create</h1>
+<form action="{{ route('posts.store') }}" method="POST">
+  @csrf
+  <input type="text" name="content" autocomplete="off" value="Hello, World!" autofocus />
+  <button>Create</button>
+</form>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/edit.blade.php example-app/resources/views/posts/edit.blade.php
--- example-app-d/resources/views/posts/edit.blade.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/edit.blade.php    2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,13 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Edit</h1>
+<form action="{{ route('posts.update', $post->id) }}" method="POST">
+  @csrf
+  @method('PUT')
+  <input type="text" name="content" autocomplete="off" value="{{ $post->content }}" autofocus />
+  <button>Update</button>
+</form>
+<form action="{{ route('posts.destroy', $post->id) }}" method="POST">
+  @csrf
+  @method('DELETE')
+  <button>Delete</button>
+</form>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/index.blade.php example-app/resources/views/posts/index.blade.php
--- example-app-d/resources/views/posts/index.blade.php 2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/index.blade.php   2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,19 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Laravel 11 CRUD</h1>
+@if ($message = Session::get('success'))
+<p>Success: {{ $message }}</p>
+@endif
+<p><a href="{{ route('posts.create') }}">Create</a></p>
+<table>
+  <tr>
+    <th>ID</th>
+    <th>Content</th>
+    <th>Control</th>
+  </tr>
+  @foreach ($posts as $post)
+  <tr>
+    <td>{{ $post->id }}</td>
+    <td>{{ $post->content }}</td>
+    <td><a href="{{ route('posts.show', $post->id) }}">Show</a> | <a href="{{ route('posts.edit', $post->id) }}">Edit</a></td>
+  </tr>
+  @endforeach
+</table>
diff -ur -x composer.lock -x vendor example-app-d/resources/views/posts/show.blade.php example-app/resources/views/posts/show.blade.php
--- example-app-d/resources/views/posts/show.blade.php  2024-08-17 12:00:00.000000000 +0000
+++ example-app/resources/views/posts/show.blade.php    2024-08-17 12:00:00.000000000 +0000
@@ -1,3 +1,3 @@
-<div>
-    <!-- ... -->
-</div>
+<h1>Show</h1>
+<p>{{ $post->content }}</p>
+<p><a href="{{ route('posts.index') }}">Back</a></p>
diff -ur -x composer.lock -x vendor example-app-d/routes/web.php example-app/routes/web.php
--- example-app-d/routes/web.php        2024-07-16 14:39:20.000000000 +0000
+++ example-app/routes/web.php  2024-08-17 12:00:00.000000000 +0000
@@ -1,7 +1,9 @@
 <?php

 use Illuminate\Support\Facades\Route;
+use App\Http\Controllers\PostController;

 Route::get('/', function () {
-    return view('welcome');
+    return '<p><a href="/posts">Post</a></p>';
 });
+Route::resource('posts', PostController::class);

Discussion