In this tutorial we’ll create Laravel 10 CRUD app using default Breeze starter kit which comprise of Blade templates along with Tailwind CSS and AlpineJS. Laravel 10.x requires a minimum PHP version of 8.1. This tutorial assumes that you are using Linux, MacOS or WSL on Windows and Node.js, Composer and PHP 8.1 along with required modules i.e bcmatch, sqlite, mbstring, xml, zip, gd, mcrypt, curl are properly installed. Also make sure that SQLite is installed as we’ll be using SQLite to keep things simple and quick; you may use MySQL or PostgreSQL as you like, however make sure the corresponding PHP module is installed too.
Using Composer, enter following command in terminal to install Laravel 10:
composer create-project laravel/laravel app 10.*
Now go inside newly created app folder by executing:
cd app
Again, using Composer, add Breeze package:
composer require laravel/breeze:* --dev
Now install Breeze package with Blade based frontend scaffolding by executing:
php artisan breeze:install blade
The above command will also install the required node.js modules and compile client-side assets (JS and CSS files).
If you want to open the project in VSCode, then enter following command:
code .
Next, setup database and Laravel environment file. Here, I’m configuring SQLite database for simplicity purposes. Being in parent folder (app), execute following command in order to create new SQLite database file:
touch database/database.sqlite
Then find .env config file in the parent folder, open it and amend DB_CONNECTION line as follows:
DB_CONNECTION = sqlite
You can remove DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD from .env when using SQLite database.
Since, initial setup of Laravel app is complete, we can start building our CRUD app. First enter the following command to create Post model along with the migration and controller:
php artisan make:model Post -mrc
Then, open database/migrations/(date_stamp)_create_posts_table.php and insert the desired table fields as follows:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Enter following command in parent folder to migrate the above migration along with Breeze authentication migrations:
php artisan migrate
Next, in order to enable mass-assignment for relevant data fields, open app/Models/Post.php model file and insert protected fillable array as follows:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title', 'body'
];
}
Now, we move onto Controller. So, open app/Http/Controllers/PostController.php and amend it as follows in order to define basic CRUD (Create, Read, Update, Delete) functions:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
class PostController extends Controller
{
public function index(): View
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function create(): View
{
return view('posts.create');
}
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'title' => ['required', 'max:50'],
'body' => ['required'],
]);
$item = Post::create($validated);
return redirect(route('posts.index'))->with('success', 'Post created.');
}
public function show(Post $post): View
{
//
}
public function edit(Post $post): View
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post): RedirectResponse
{
$validated = $request->validate([
'title' => ['required', 'max:50'],
'body' => ['required'],
]);
$post->update($validated);
return redirect(route('posts.index'))->with('success', 'Post updated.');
}
public function destroy(Post $post): RedirectResponse
{
$post->delete();
return redirect(route('posts.index'))->with('success', 'Post deleted.');
}
}
In order to define resource route, open routes/web.php and put the following line at the upper side of it:
use App\Http\Controllers\PostController;
And then, insert following resource route down somewhere into this routes/web.php:
Route::resource('posts', PostController::class)->middleware(['auth', 'verified']);
We’re done with the Model and Controller parts of our app. Now let’s create Views using Blade templates.
First, create posts folder inside resources/views. Being inside parent folder (app), you may execute following command to make this folder:
mkdir resources/views/posts
Now create following three files inside resources/views/posts folder:
- index.blade.php
- create.blade.php
- edit.blade.php
You can execute following command inside parent folder in order to create these files in one go:
touch resources/views/posts/{index,create,edit}.blade.php
Go inside resources/views/posts folder and insert following code inside index.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Posts') }}
</h2>
</x-slot>
@if(session()->get('success'))
<div
x-data="{ show: true }"
x-show="show"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 transform scale-90"
class="p-1 m-1 bg-indigo-200 border-b border-indigo-400 rounded-md inline-flex">
{{ session()->get('success') }}
<button @click="show = false" class="inline-flex font-extrabold text-indigo-900 focus:outline-none focus:text-indigo-500 transition ease-in-out duration-150">
  X
</button>
</div>
@endif
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-4 lg:px-4">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<a href="{{ route('posts.create')}}" class="inline-flex items-center px-4 py-2 mb-4 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150">New Post</a>
<table class="shadow-lg">
<thead>
<tr class="bg-gray-400 text-white font-extrabold" >
<td class="px-4 py-1 text-center" >ID</td>
<td class="px-4 py-1">Title</td>
<td class="px-4 py-1">Body</td>
<td class="px-4 py-1 text-center w-2/6" colspan = '2'>Actions</td>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td class="border px-4 py-1 text-center">{{ $post->id }}</td>
<td class="border px-4 py-1">{{ $post->title }}</td>
<td class="border px-4 py-1">{{ $post->body }}</td>
<td class="border px-4 py-1">
<a href="{{ route('posts.edit',$post->id)}}" class="inline-flex items-center px-4 py-1 bg-green-800 border border-transparent rounded-lg font-semibold text-xs text-white tracking-widest hover:bg-green-700 active:bg-green-900 focus:outline-none focus:border-green-900 focus:ring ring-green-300 disabled:opacity-25 transition ease-in-out duration-150">Edit</a>
</td>
<td class="border px-4 py-1">
<form action="{{ route('posts.destroy', $post->id)}}" method="post">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center px-4 py-1 bg-red-800 border border-transparent rounded-lg font-semibold text-xs text-white tracking-widest hover:bg-red-700 active:bg-red-900 focus:outline-none focus:border-red-900 focus:ring ring-red-300 disabled:opacity-25 transition ease-in-out duration-150">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>
Then, insert following code in create.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Posts') }}
</h2>
</x-slot>
<x-input-error :messages="$errors->all()" />
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-4 lg:px-4">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="post" action="{{ route('posts.store') }}">
@csrf
<div class="p-2 flex items-left">
<x-input-label for="title" class="w-20">Title:</x-input-label>
<x-text-input name="title"/>
</div>
<div class="p-2 flex items-center">
<x-input-label for="body" class="w-20">Body:</x-input-label>
<x-text-input name="body"/>
</div>
<div class="p-6">
<x-primary-button> {{ __('Add Post') }} </x-primary-button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
And then, insert following code in edit.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Posts') }}
</h2>
</x-slot>
<x-input-error :messages="$errors->all()" />
<div class="py-6">
<div class="max-w-7xl mx-auto sm:px-4 lg:px-4">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form method="post" action="{{ route('posts.update', $post) }}">
@method('PUT')
@csrf
<div class="p-2 flex items-center">
<x-input-label for="title" class="w-20">Title:</x-input-label>
<x-text-input name="title" value="{{ $post->title }}"/>
</div>
<div class="p-2 flex items-center">
<x-input-label for="body" class="w-20">Body:</x-input-label>
<x-text-input name="body" value="{{ $post->body }}"/>
</div>
<div class="p-6">
<x-primary-button> {{ __('Edit Post') }} </x-primary-button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
Now execute following command in parent folder to compile UI assets:
npm run build
Finally, execute following command to run PHP dev server:
php artisan serve
Open browser and enter following URL in address bar (don’t forget to create a user login first):
http://localhost:8000/posts