- Published on
CRUD with Laravel Livewire 3
For some individuals, constructing applications using full-stack can feel quite challenging, especially when dealing with multiple frameworks simultaneously like Vue.js or React.js. The good news is, there is now a full-stack framework available that allows us to create application interfaces using just Laravel – it's called Laravel Livewire.
What is Laravel Livewire
Laravel Livewire is a framework for Laravel that enables us to build interactive web interfaces using server-side components effortlessly. With Livewire, we can merge server-side and client-side code within a single component, thereby reducing complexity and enhancing productivity in web interface development.
Key Features of Laravel Livewire
Laravel Livewire offers several prominent features:
- Server-Side Based Components: We can create components using PHP code to manage logic and presentation of elements on web pages. These components can directly interact with data and events on the server side.
- Automatic Reactivity: Livewire automatically handles data changes and interactions. When there are interactions or data changes in components, Livewire will automatically update the web interface without the need for additional JavaScript code.
- Real-Time Communication: Livewire utilizes AJAX technology for real-time communication with the server without the need to reload the entire page. This enables us to build more responsive and interactive web applications.
- Integration with Laravel: Livewire is designed to seamlessly integrate with the Laravel framework. We can leverage Laravel features like Eloquent, Validation, Middleware, and more within Livewire components.
- SEO Friendly: By utilizing server-side rendering, Livewire generates web pages that are more SEO-friendly. The main content is available in the HTML source code when the page is initially loaded, contributing to better search engine optimization.
Laravel Livewire offers an easier way to build interactive web applications compared to using JavaScript frameworks like Vue or React. This allows Laravel developers to focus on business logic and development without spending excessive time dealing with the complexities of user interface management.
As of August 25, 2023, Laravel Livewire v3.0.0 has been released with various updates, which you can explore in detail in its official documentation. Since the draft version of this tutorial uses an older Livewire version, we will switch to using Laravel Livewire 3 in the released version of this tutorial.
Overview
In this Laravel 10 tutorial, we will explore the use of Laravel Livewire 3 to build a simple project with CRUD functionality. As for the data used in this project, we will employ the usual post
data. The ultimate goal, as the title of this tutorial suggests, is for our project to perform CRUD operations, enabling it to display post data, create new posts, update existing posts, and delete post data.
Step 1 - Setting Up the Project
First, let's create a new Laravel project using composer
. Open your terminal and execute the following command below.
composer create-project --prefer-dist laravel/laravel crud_livewire
Step 2 - Configure the Database Settings
Next, navigate to the root directory of our project using the command below.
cd crud_livewire
After that, open the .env
file in a text editor and adjust the database credentials accordingly.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_crud_livewire
DB_USERNAME=admin
DB_PASSWORD=password
Make sure the credentials and the database name are in line with what we are going to use. Then, save the .env
file again.
Step 3 - Installing Laravel Livewire 3
The next step is to install the Laravel Livewire 3 package. Open the terminal again, then install livewire
through composer
.
composer require livewire/livewire
Wait until the process of downloading the livewire
package is complete.
Let's check in the composer.json
file. At the time of writing this tutorial, the installed version of Livewire is version 3.
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"livewire/livewire": "^3.0"
},
Step 4 - Create Model and Migration Files
Our project preparation is complete, the next step is to create model and migration files. We will generate the model and migration files using the following artisan
command.
php artisan make:model Post -m
Output when we run the command above:
$ php artisan make:model Post -m
INFO Model [app/Models/Post.php] created successfully.
INFO Migration [database/migrations/2023_07_28_025654_create_posts_table.php] created successfully.
After the migration file is successfully generated, let's open the database/migrations/20xx_xx_xx_xxxxxx_create_posts_table.php
file, and then adjust it with the following code snippet:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('slug');
$table->smallInteger('status');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Once done, save the migration file again and don't forget to run the migration using the artisan
command.
php artisan migrate
Output:
$ php artisan migrate
INFO Preparing database.
Creating migration table ......................................... 11ms DONE
INFO Running migrations.
2014_10_12_000000_create_users_table ............................. 13ms DONE
2014_10_12_100000_create_password_reset_tokens_table ............. 27ms DONE
2019_08_19_000000_create_failed_jobs_table ....................... 24ms DONE
2019_12_14_000001_create_personal_access_tokens_table ............ 30ms DONE
2023_07_28_025654_create_posts_table ............................. 13ms DONE
Alright, the posts
table is now ready in our database.
Next, let's open the second file, which is the model file app/Models/Post.php
. In this file, we'll add the $fillable
attribute to manage the allowed mass assignments.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title', 'content', 'slug', 'status'
];
}
Step 5 - Create Post Component
In this step, we're diving into using Livewire
. Now, let's create a Livewire component using the command below.
php artisan make:livewire post
Output:
$ php artisan make:livewire post
COMPONENT CREATED 🤙
CLASS: app/Http/Livewire/Post.php
VIEW: resources/views/livewire/post.blade.php
Next, open the file app/Http/Livewire/Post.php
, then add the following lines of code.
<?php
namespace App\Livewire;
use Illuminate\Support\Str;
use Livewire\Component;
class Post extends Component
{
/**
* define public variable
*/
public $title, $content, $postId, $slug, $status, $updatePost = false, $addPost = false;
/**
* List of add/edit form rules
*/
protected $rules = [
'title' => 'required',
'content' => 'required',
'status' => 'required'
];
/**
* Reseting all inputted fields
* @return void
*/
public function resetFields()
{
$this->title = '';
$this->content = '';
$this->status = 1;
}
/**
* render the post data
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function render()
{
$posts = \App\Models\Post::latest()->get();
return view('livewire.post', compact('posts'));
}
/**
* Open Add Post form
* @return void
*/
public function create()
{
$this->resetFields();
$this->addPost = true;
$this->updatePost = false;
}
/**
* store the user inputted post data in the posts table
* @return void
*/
public function store()
{
$this->validate();
try {
\App\Models\Post::create([
'title' => $this->title,
'content' => $this->content,
'status' => $this->status,
'slug' => Str::slug($this->title)
]);
session()->flash('success', 'Post Created Successfully!!');
$this->resetFields();
$this->addPost = false;
} catch (\Exception $ex) {
session()->flash('error', 'Something goes wrong!!');
}
}
/**
* show existing post data in edit post form
* @param mixed $id
* @return void
*/
public function edit($id)
{
try {
$post = \App\Models\Post::findOrFail($id);
if (!$post) {
session()->flash('error', 'Post not found');
} else {
$this->title = $post->title;
$this->content = $post->content;
$this->status = $post->status;
$this->postId = $post->id;
$this->updatePost = true;
$this->addPost = false;
}
} catch (\Exception $ex) {
session()->flash('error', 'Something goes wrong!!');
}
}
/**
* update the post data
* @return void
*/
public function update()
{
$this->validate();
try {
\App\Models\Post::whereId($this->postId)->update([
'title' => $this->title,
'content' => $this->content,
'status' => $this->status,
'slug' => Str::slug($this->title)
]);
session()->flash('success', 'Post Updated Successfully!!');
$this->resetFields();
$this->updatePost = false;
} catch (\Exception $ex) {
session()->flash('success', 'Something goes wrong!!');
}
}
/**
* Cancel Add/Edit form and redirect to post listing page
* @return void
*/
public function cancel()
{
$this->addPost = false;
$this->updatePost = false;
$this->resetFields();
}
/**
* delete specific post data from the posts table
* @param mixed $id
* @return void
*/
public function destroy($id)
{
try {
\App\Models\Post::find($id)->delete();
session()->flash('success', "Post Deleted Successfully!!");
} catch (\Exception $e) {
session()->flash('error', "Something goes wrong!!");
}
}
}
Once done, save the app/Http/Livewire/Post.php
file again.
Next, let's create a new file resources/views/home.blade.php
, then add the following lines of code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Belajar Laravel 10 - Crud Laravel Livewire @ qadrlabs.com</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
@livewireStyles
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">Livewire</a>
</div>
</nav>
<div class="container">
<div class="row justify-content-center mt-3">
@livewire('post')
</div>
</div>
@livewireScripts
</body>
</html>
To display the list of posts, open the component file resources/views/livewire/post.blade.php
, then adjust the content of the post.blade.php
file to match the following code lines.
<div>
<div class="col-md-12 mb-2">
@if(session()->has('success'))
<div class="alert alert-success" role="alert">
{{ session()->get('success') }}
</div>
@endif
@if(session()->has('error'))
<div class="alert alert-danger" role="alert">
{{ session()->get('error') }}
</div>
@endif
@if($addPost)
@include('livewire.create')
@endif
@if($updatePost)
@include('livewire.update')
@endif
</div>
<div class="col-md-12">
<div class="card">
<div class="card-header">
@if(!$addPost)
<button wire:click="create()" class="btn btn-primary btn-sm float-end">Add New Post</button>
@endif
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Content</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse ($posts as $post)
<tr>
<td>
{{$post->title}}
</td>
<td>
{{$post->content}}
</td>
<td>{{ $post->status == 1 ? 'Draft':'Publish' }}</td>
<td>
<button wire:click="edit({{$post->id}})"
class="btn btn-primary btn-sm">Edit</button>
<button wire:click="destroy({{ $post->id }})"
class="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
@empty
<tr>
<td colspan="4" align="center">
No Posts Found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
Now let's create a new component file to display a form for adding data, named resources/views/livewire/create.blade.php
. Then type the following code lines.
<div class="card">
<div class="card-body">
<form>
<div class="form-group mb-3">
<label for="title">Title:</label>
<input type="text" class="form-control @error('title') is-invalid @enderror" id="title"
placeholder="Enter Title" wire:model="title">
@error('title')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="form-group mb-3">
<label for="content">content:</label>
<textarea class="form-control @error('content') is-invalid @enderror" id="content"
wire:model="content" placeholder="Enter content"></textarea>
@error('content')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="form-group mb-3">
<label for="status">Status:</label>
<select name="status" id="status" class="form-control @error('status') is-invalid @enderror" wire:model="status">
<option value="1">Draft</option>
<option value="2">Publish</option>
</select>
@error('status')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="d-grid gap-2">
<button wire:click.prevent="store()" class="btn btn-success btn-block">Save</button>
<button wire:click.prevent="cancel()" class="btn btn-secondary btn-block">Cancel</button>
</div>
</form>
</div>
</div>
Let's create the second component file to handle the data update process, named resources/views/livewire/update.blade.php
. Adjust it with the following code lines.
<div class="card">
<div class="card-body">
<form>
<div class="form-group mb-3">
<label for="title">Title:</label>
<input type="text" class="form-control @error('title') is-invalid @enderror" id="title"
placeholder="Enter Title" wire:model="title">
@error('title')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="form-group mb-3">
<label for="content">content:</label>
<textarea class="form-control @error('content') is-invalid @enderror" id="content"
wire:model="content" placeholder="Enter content"></textarea>
@error('content')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="form-group mb-3">
<label for="status">Status:</label>
<select name="status" id="status" class="form-control @error('status') is-invalid @enderror" wire:model="status">
<option value="1">Draft</option>
<option value="2">Publish</option>
</select>
@error('status')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="d-grid gap-2">
<button wire:click.prevent="update()" class="btn btn-success btn-block">Update</button>
<button wire:click.prevent="cancel()" class="btn btn-secondary btn-block">Cancel</button>
</div>
</form>
</div>
</div>
Step 6 - Define Route
Open the file routes/web.php
, then define the route to display our project page.
Route::get('/', function () {
return view('home');
});
Step 7 - Test Our Project
Next, let's run our project. Open the terminal again and run the following command:
php artisan serve
After that, open the link http://127.0.0.1:8000
in your browser and you can try adding, updating, and deleting data.
Conclusion
In this tutorial, we have successfully created a simple project with CRUD features using the Laravel Livewire package. After trying to add data, edit data, and delete data, you might have noticed a difference in the interface of our project and found it interesting to explore further. For instance, you could consider adding some common features like search, sorting, and pagination.
What do you think? Are you interested in giving it a try?