DEV Community

Cover image for Laravel Livewire CRUD with Breeze & Tailwind CSS
saim
saim

Posted on • Originally published at larainfo.com

Laravel Livewire CRUD with Breeze & Tailwind CSS

Today, we'll create a Laravel application to perform CRUD operations (create, read, update, upload, and delete) with Laravel and Livewire using Laravel Breeze. Why not Jetstream? Because Laravel Breeze is much simpler and easier to understand, plus it comes with Tailwind CSS and Alpine.js. So let's get started.

Step 1: Create Laravel Project

Installing a fresh new laravel application.

composer create-project --prefer-dist laravel/laravel project-name
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Database Details in ENV

Now, you have to connect the laravel app to the database, hence open the .env configuration file and add the database credentials as suggested below.
.env

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
Enter fullscreen mode Exit fullscreen mode

Step 3: Install breeze & Setup Livewire

You need to install first breeze package.

composer require laravel/breeze --dev
Enter fullscreen mode Exit fullscreen mode

Then, install breeze ui.

php artisan breeze:install
Enter fullscreen mode Exit fullscreen mode

Install livewire

composer require livewire/livewire
Enter fullscreen mode Exit fullscreen mode

Include the JavaScript (on every page that will be using Livewire).
resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel') }}</title>
    <!-- Fonts -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
    <!-- Styles -->
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    @livewireStyles
</head>
<body class="font-sans antialiased">
    <div class="min-h-screen bg-gray-100">
        @include('layouts.navigation')

        <!-- Page Heading -->
        <header class="bg-white shadow">
            <div class="px-4 py-6 mx-auto max-w-7xl sm:px-6 lg:px-8">
                {{ $header }}
            </div>
        </header>

        <!-- Page Content -->
        <main>
            {{ $slot }}
        </main>
    </div>
    @livewireScripts
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

You can install dependencies.

npm install && npm run dev
Enter fullscreen mode Exit fullscreen mode

Step 4: Perform CRUD operations

Create Model and Migration

php artisan make:model Post -m
Enter fullscreen mode Exit fullscreen mode

Migration File

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable 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');
    }
}
Enter fullscreen mode Exit fullscreen mode

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'
    ];
}
Enter fullscreen mode Exit fullscreen mode

Run the migration command.

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Create Post Controller

php artisan make:controller PostController
Enter fullscreen mode Exit fullscreen mode

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('posts');
    }
}
Enter fullscreen mode Exit fullscreen mode

Create Route

<?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('posts', [PostController::class,'index'])->name('posts.index');

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__ . '/auth.php';
Enter fullscreen mode Exit fullscreen mode

Create Livewire Component

php artisan make:livewire PostForm
Enter fullscreen mode Exit fullscreen mode

Use Component file

resources/views/postsblade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="text-xl font-semibold leading-tight text-gray-800">
            Post
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
            <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    <livewire:post-form />
                </div>
            </div>
        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Next you need to update PostForm.php file.
app/Http/Livewire/PostForm.php

<?php

namespace App\Http\Livewire;

use App\Models\Post;
use Livewire\Component;
use Livewire\WithPagination;

class PostForm extends Component
{
    use WithPagination;

    public $title;
    public $description;
    public $post_id;

    protected $rules = [
        'title' => 'required',
        'description' => 'required',
    ];

    public function storePost()
    {
        $this->validate();
        $post = Post::create([
            'title' => $this->title,
            'description' => $this->description
        ]);
        $this->reset();
    }

    public function edit($id)
    {
        $post = Post::find($id);
        $this->post_id = $post->id;
        $this->title = $post->title;
        $this->description = $post->description;
    }

    public function update()
    {
        $post = Post::updateOrCreate(
            [
                'id'   => $this->post_id,
            ],
            [
                'title' => $this->title,
                'description' => $this->description
            ],

        );

        $this->reset();
    }

    public function destroy($id)
    {
        Post::destroy($id);
    }


    public function render()
    {
        return view('livewire.post-form', ['posts' => Post::latest()->paginate(10)]);
    }
}
Enter fullscreen mode Exit fullscreen mode

Use View File

resources/views/livewire/post-form.blade.php

<div>
    <h4 class="mb-4 text-2xl font-bold">Post </h4>
    <div>
        <div class="container mx-auto">
            <form method="POST" wire:submit.prevent="storePost">
                @csrf
                <div>
                    <label for="title">Title</label>
                    <input type="text" wire:model.lazy="title" class="w-full py-2 rounded">
                    @error('title')
                    <span class="text-red-600">{{ $message }}</span>
                    @enderror
                </div>
                <div class="mt-8">
                    <label class="block mb-2 text-xl">Description </label>
                    <textarea wire:model.lazy="description" rows="3" cols="20" class="w-full rounded">
                </textarea>
                    @error('description')
                    <span class="text-red-600">{{ $message }}</span>
                    @enderror
                </div>
                <button type="submit" class="px-4 py-2 mt-4 text-white bg-blue-600 rounded">
                    Submit
                </button>
                <button type="submit" wire:click="update" class="px-4 py-2 text-white bg-indigo-600 rounded">
                    Update
                </button>
            </form>
        </div>
        <div class="flex flex-col mt-8">
            <div class="py-2">
                <div class="min-w-full border-b border-gray-200 shadow">
                    <table class="min-w-full">
                        <thead>
                            <tr>
                                <th class="px-6 py-3 text-left text-gray-500 border-b border-gray-200 bg-gray-50">
                                    Id
                                </th>
                                <th class="px-6 py-3 text-left text-gray-500 border-b border-gray-200 bg-gray-50">
                                    Title
                                </th>
                                <th class="px-6 py-3 text-left text-gray-500 border-b border-gray-200 bg-gray-50">
                                    Edit
                                </th>
                                <th class="px-6 py-3 text-left text-gray-500 border-b border-gray-200 bg-gray-50">
                                    Delete
                                </th>
                            </tr>
                        </thead>

                        <tbody class="bg-white">
                            @foreach($posts as $post)
                            <tr>
                                <td class="px-6 py-4 border-b border-gray-200">
                                    <div class="flex items-center">
                                        <div class="ml-4">
                                            <div class="text-sm text-gray-900">
                                                {{ $post->id }}
                                            </div>
                                        </div>
                                    </div>
                                </td>

                                <td class="px-6 py-4 border-b border-gray-200">
                                    <div class="text-sm text-gray-900">
                                        {{ $post->title }}
                                    </div>
                                </td>

                                <td class="px-6 py-4 border-b border-gray-200">
                                    <button wire:click="edit({{ $post->id }})" class="px-4 py-2 text-white bg-blue-600">
                                        Edit
                                    </button>
                                </td>

                                <td class="px-6 py-4 text-sm text-gray-500 border-b border-gray-200">
                                    <button wire:click="destroy({{ $post->id }})" class="px-4 py-2 text-white bg-red-600">
                                        Delete
                                    </button>
                                </td>

                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
                {{ $posts->links() }}
            </div>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

laravel livewire tailwind crud

Step 5: Serve The Application

Now, your application let’s serve the application.

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Now you have to open bellow URL with your browser:

http://localhost:8000/posts
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
warwait profile image
Parker Waiters

This guide is very thorough—thanks for sharing! Quick question: why did you choose Laravel Breeze over Jetstream for this CRUD setup? Keep up the good work!