In this tutorial, we will create a Laravel CRUD application with Tailwind CSS 3 using the Laravel Breeze package. Breeze is a first-party Laravel package that provides various features such as authentication and integration with the modern Tailwind CSS 3 framework. It also includes the Tailwind first-party plugin for forms.
Step 1: Install Laravel & Connect Database
To install a fresh new Laravel 9 application, navigate to your terminal, type the command, and create a new Laravel app.
composer create-project laravel/laravel project-name
Now, you need to connect the Laravel app to the database. Open the .env configuration file and add the database credentials as suggested below.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database_name
DB_USERNAME=database_user_name
DB_PASSWORD=database_password
Step 2: Install Breeze For Tailwind CSS 3
Install laravel breeze via composer.
composer require laravel/breeze --dev
Next, run below command.
php artisan breeze:install
And final install Dependencies.
npm install && npm run dev
# and migrate
php artisan migrate
Step 3: Create Model Request and Resource Controller & Route
Create Post Modal.
php artisan make:model Post -mcr
Create Post Request.
php artisan make:request PostRequest
post_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
};
app/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'description'];
}
app/Http/Requests/PostRequest.php
<?php
namespace App\\Http\\Requests;
use Illuminate\\Foundation\\Http\\FormRequest;
class PostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => ['required', 'max:80'],
'description' => ['required']
];
}
}
app/Http/Controllers/PostController.php
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$posts = Post::latest()->paginate(10);
return view('posts.index', compact('posts'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('posts.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(PostRequest $request)
{
Post::create($request->validated());
return redirect()->route('posts.index')->with('message', 'Post Created Successfully');
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function update(PostRequest $request, Post $post)
{
$post->update([
'title' => $request->title,
'description' => $request->description
]);
return redirect()->route('posts.index')->with('message', 'Post Updated Successfully');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('message', 'Post Deleted Successfully');
}
}
Create Post routes.
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
Route::resource('posts',PostController::class);
require __DIR__.'/auth.php';
Step 4: Create blade view file for CRUD
resources/views/posts/create.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="font-sans antialiased">
<div class="flex flex-col items-center min-h-screen pt-6 bg-gray-100 sm:justify-center sm:pt-0">
<div class="w-full px-16 py-20 mt-6 overflow-hidden bg-white rounded-lg lg:max-w-4xl">
<div class="mb-4">
<h1 class="font-serif text-3xl font-bold">Create Post</h1>
</div>
<div class="w-full px-6 py-4 bg-white rounded shadow-md ring-1 ring-gray-900/10">
<form method="POST" action="{{ route('posts.index') }}">
@csrf
<!-- Title -->
<div>
<label class="block text-sm font-medium text-gray-700" for="title">
Title
</label>
<input
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm placeholder:text-gray-400 placeholder:text-right focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
type="text" name="title" placeholder="180" value="{{old('title')}}">
@error('title')
<span class="text-red-600 text-sm">
{{ $message }}
</span>
@enderror
</div>
<!-- Description -->
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700" for="description">
Description
</label>
<textarea name="description"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm placeholder:text-gray-400 placeholder:text-right focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
rows="4" placeholder="400"> {{old('description')}}</textarea>
@error('description')
<span class="text-red-600 text-sm">
{{ $message }}
</span>
@enderror
</div>
<div class="flex items-center justify-start mt-4">
<button type="submit"
class="inline-flex items-center px-6 py-2 text-sm font-semibold rounded-md text-sky-100 bg-sky-500 hover:bg-sky-700 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300">
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
resources/views/posts/index.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Dashboard') }}
</h2>
</x-slot>
<!-- Index Post -->
<!-- Index Post -->
<div class="container max-w-6xl mx-auto mt-20">
<div class="mb-4">
<h1 class="font-serif text-3xl font-bold underline decoration-gray-400"> Post Index</h1>
@if (session()->has('message'))
<div class="p-3 rounded bg-green-500 text-green-100 my-2">
{{ session('message') }}
</div>
@endif
<div class="flex justify-end">
<a href="{{ route('posts.create')}}"
class="px-4 py-2 rounded-md bg-sky-500 text-sky-100 hover:bg-sky-600">Create Post</a>
</div>
</div>
<div class="flex flex-col">
<div class="overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div
class="inline-block min-w-full overflow-hidden align-middle border-b border-gray-200 shadow sm:rounded-lg">
<table class="min-w-full">
<thead>
<tr>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
ID</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Title</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Description</th>
<th
class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-gray-500 uppercase border-b border-gray-200 bg-gray-50">
Created_At</th>
<th class="px-6 py-3 text-sm text-left text-gray-500 border-b border-gray-200 bg-gray-50"
colspan="2">
Action</th>
</tr>
</thead>
<tbody class="bg-white">
@foreach ($posts as $post)
<tr>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="flex items-center">
{{ $post->id }}
</div>
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
<div class="text-sm leading-5 text-gray-900">
{{ $post->title }}
</div>
</td>
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
{{ $post->description }}
</td>
<td
class="px-6 py-4 text-sm leading-5 text-gray-500 whitespace-no-wrap border-b border-gray-200">
<span> {{ $post->created_at }}</span>
</td>
<td
class="text-sm font-medium leading-5 text-center whitespace-no-wrap border-b border-gray-200 ">
<a href="{{ route('posts.edit', $post->id) }}"
class="text-indigo-600 hover:text-indigo-900">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</a>
</td>
<td class="text-sm font-medium leading-5 whitespace-no-wrap border-b border-gray-200 ">
<form action="{{ route('posts.destroy',$post->id) }}" method="POST" onsubmit="return confirm('{{ trans('are You Sure ? ') }}');">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<button type="submit" class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-red-600 hover:text-red-800 cursor-pointer" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>
resources/views/posts/edit.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="text-xl font-semibold leading-tight text-gray-800">
{{ __('Dashboard') }}
</h2>
</x-slot>
<div class="font-sans antialiased">
<div class="flex flex-col items-center min-h-screen pt-6 bg-gray-100 sm:justify-center sm:pt-0">
<div class="w-full px-16 py-20 mt-6 overflow-hidden bg-white rounded-lg lg:max-w-4xl">
<div class="mb-4">
<h1 class="font-serif text-3xl font-bold">Update Post</h1>
</div>
<div class="w-full px-6 py-4 bg-white rounded shadow-md ring-1 ring-gray-900/10">
<form method="POST" action="{{ route('posts.update',$post->id) }}">
@csrf
@method('PUT')
<!-- Title -->
<div>
<label class="block text-sm font-medium text-gray-700" for="title">
Title
</label>
<input
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm placeholder:text-gray-400 placeholder:text-right focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
type="text" name="title" placeholder="180" value="{{old('title',$post->title)}}">
@error('title')
<span class="text-red-600 text-sm">
{{ $message }}
</span>
@enderror
</div>
<!-- Description -->
<div class="mt-4">
<label class="block text-sm font-medium text-gray-700" for="description">
Description
</label>
<textarea name="description"
class="block w-full mt-1 border-gray-300 rounded-md shadow-sm placeholder:text-gray-400 placeholder:text-right focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
rows="4" placeholder="400"> {{old('description',$post->description)}}</textarea>
@error('description')
<span class="text-red-600 text-sm">
{{ $message }}
</span>
@enderror
</div>
<div class="flex items-center justify-start mt-4">
<button type="submit"
class="inline-flex items-center px-6 py-2 text-sm font-semibold rounded-md text-sky-100 bg-sky-500 hover:bg-sky-700 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300">
Update
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>