In this article, we are going to create a basic CRUD using Laravel 8, Livewire 2.x.
Table of contents
- Create new Laravel project
- Set up database credentials
- Install Laravel UI
- Post model, migration, factory and seeder
- Install Laravel Livewire
- Create livewire components
- Add livewire routes
- Set up Post Index component
- Create Post Component
- Edit Post Component
- Delete Post
- Github Repo
1. Create new Laravel project
Let's create a new project called Laravel-Livewire-CRUD with the following command:
composer create-project laravel/laravel Laravel-Livewire-CRUD
Or you can use the global installer
laravel new Laravel-Livewire-CRUD
2. Set up database credentials.
In the .env file, modify the next lines with your credentials
3. Install Laravel UI
In this tutorial, I'm going to use Laravel UI but you can use Laravel Jetstream, Breeze, or Fortify.
Use this command to install Laravel UI
composer require laravel/ui
After that, we need to install the frontend scaffold with registration using this command
php artisan ui bootstrap --auth
If everything is correct, execute
npm install && npm run dev
The next step is migrate our tables with php artisan migrate command, when you complete this step, you should be able to see the register view.
Cool. Now we can log in.
4. Post model, migration, factory and seeder
We have to create a Post model with its migration, factory, and seeder
php artisan make:model Post -mfs
Next, we have to search posts migration inside the database folder
And inside the migration add the following lines
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('post_text');
$table->timestamps();
});
}
Now, in our model add the next lines
protected $fillable = [
'title',
'post_text'
];
That's it, our model and migration are ready, the next step is go to app/database/factories/PostFactory.php file and inside the definition function add
return [
'title' => $this->faker->words(3, true),
'post_text' => $this->faker->paragraph()
];
Alright almost done here, now we go to app/database/seeders/PostSeeder.php file, add this
public function run()
{
Post::factory()->count(5)->create();
}
Finally, in app/database/seeders/DatabaseSeeder.php, add
public function run()
{
$this->call([
PostSeeder::class,
]);
}
Great!!! everything is ready, now execute php artisan migrate --seed
. We're done here. The next step is Livewire integration.
5. Install Laravel Livewire
composer require livewire/livewire
After successfully installing, you have to search resources/views/layouts/app.blade.php file and add @livewireStyles
, @livewireScripts
like the image.
6. Create livewire components
Let's create 3 components: Post Index, Post Create, Post Edit.
php artisan make:livewire posts.index
php artisan make:livewire posts.create
php artisan make:livewire posts.edit
Each component generates two files in our project
7. Add livewire routes
In web.php file, let's define the routes of the project.
Route::get('/posts/index', App\Http\Livewire\Posts\Index::class)->name('posts.index');
Route::get('/posts/create', App\Http\Livewire\Posts\Create::class)->name('posts.create');
Route::get('/posts/edit/{post}', App\Http\Livewire\Posts\Edit::class)->name('posts.edit');
8. Set up Post Index component
Before configure the Post Index component we need to add Post navigation in app/resources/views/layouts/app.blade.php like the image below.
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<a class="navbar-brand" href="{{ route('posts.index') }}">
Post
</a>
</div>
</nav>
You should see this menu bar
Now, we can continue. Remember Livewire generates two files, first let's go to app/Http/Livewire/Posts/Index.php and modify like the image below.
<?php
namespace App\Http\Livewire\Posts;
use App\Models\Post;
use Livewire\Component;
use Livewire\WithPagination;
class Index extends Component
{
use WithPagination;
protected $paginationTheme = 'bootstrap';
protected $listeners = ['deletePost'];
public function deletePost($id)
{
Post::find($id)->delete();
session()->flash('message', 'Post successfully deleted.');
}
public function render()
{
$posts = Post::paginate(5);
return view('livewire.posts.index', ['posts' => $posts])
->extends('layouts.app')
->section('content');
}
}
Let me explain some things here:
Laravel Livewire can handle pagination through
withPagination
trait.Livewire uses TailwindCSS as the default style of pagination, in this case, we want to use Bootstrap and we have to add
$paginationTheme
.In the render method is necessary to add ->extends and ->section to specify where the component will render. if you want more info you can check Full-Page Components
Livewire Official Docs.
We have a listener
deletePost
, this listener becomes active when the user clicks the button to delete one Post, this action automatically sends an event with the Id we want to delete. You can understand more with theposts/index.blade.php
view down below.
After that, let's go to our view app/resources/views/livewire/posts/index.blade.php and create something like that.
<div>
<div class="container">
@if (session()->has('message'))
<div class="alert alert-success">
{{ session('message') }}
<button type="button" class="close" data-dismiss="alert">x</button>
</div>
@endif
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<div class="float-right">
<a href="{{route('posts.create')}}" class="btn btn-primary"> Create new post </a>
</div>
</div>
<div class="card-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Post text</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@forelse ($posts as $post)
<tr>
<td>{{$post->title}}</td>
<td>{{$post->post_text}}</td>
<td>
<a href="{{route('posts.edit', $post->id)}}" class="btn btn-sm btn-success"> Edit </a>
<button type="button" onclick="deletePost({{$post->id}})" class="btn btn-sm btn-danger">Delete</button>
</td>
</tr>
@empty
<tr>
<td colspan="3">No posts found</td>
</tr>
@endforelse
</tbody>
</table>
{{$posts->links()}}
</div>
</div>
</div>
</div>
</div>
<script>
function deletePost(id) {
if (confirm("Are you sure to delete this record?"))
Livewire.emit('deletePost', id);
}
</script>
</div>
Note: Livewire.emit fire an event called deletePost that we receive in Posts/Index.php. If you want to learn more about Livewire Events visit Livewire Events
Great!!!, you should see something like that.
9. Create Post Component
The next step is to search app/Http/Livewire/Posts/Create.php file and modify like the image below.
<?php
namespace App\Http\Livewire\Posts;
use App\Models\Post;
use Livewire\Component;
class Create extends Component
{
public $title, $post_text;
protected $rules = [
'title' => 'required',
'post_text' => 'required',
];
public function store()
{
Post::create($this->validate());
session()->flash('message', 'Post successfully created.');
return redirect()->route('posts.index');
}
public function render()
{
return view('livewire.posts.create')
->extends('layouts.app')
->section('content');
}
}
Right now we want to modify the app/resources/views/livewire/posts/create.blade.php view with our form to create a new post.
<div>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Create new post</div>
<div class="card-body">
<form wire:submit.prevent="store">
<div class="form-group">
<label for="title">Title</label>
<input type="text" wire:model="title" class="form-control" id="title" placeholder="Enter a title">
@error('title') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<div class="form-group">
<label for="post_text">Post text</label>
<textarea cols="80" rows="6" class="form-control" wire:model="post_text" name="post_text"></textarea>
@error('post_text') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="{{route('posts.index')}}" class="btn btn-danger">Back</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
Note: every time we want to create a new post wire:submit.prevent="store" execute the method store in our Posts/Create.php file
10. Edit Post Component
It's almost the same as that Create component but in the Edit.php file, we need to edit one Post in particular, that's why we use Route Model Binding through public Post $post variable and mount function to initialize the values of the Post.
<?php
namespace App\Http\Livewire\Posts;
use App\Models\Post;
use Livewire\Component;
class Edit extends Component
{
public Post $post;
public $title;
public $post_text;
public function mount()
{
$this->title = $this->post->title;
$this->post_text = $this->post->post_text;
}
protected $rules = [
'title' => [
'required'
],
'post_text' => [
'required'
]
];
public function update()
{
$this->post->update($this->validate());
session()->flash('message', 'Post successfully updated.');
return redirect()->route('posts.index');
}
public function render()
{
return view('livewire.posts.edit')
->extends('layouts.app')
->section('content');
}
}
Next step is to search the app/resources/views/livewire/posts/edit.blade.php view and add this code.
<div>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-header">Update post</div>
<div class="card-body">
<form wire:submit.prevent="update">
<div class="form-group">
<label for="title">Title</label>
<input type="text" wire:model="title" class="form-control" id="title" placeholder="Enter a title">
@error('title') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<div class="form-group">
<label for="post_text">Post text</label>
<textarea cols="80" rows="6" class="form-control" wire:model="post_text" name="post_text">
</textarea>
@error('post_text') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<a href="{{route('posts.index')}}" class="btn btn-danger">Back</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
11. Delete post
To be honest, we can delete a post when setup the Posts/Index.php component but let's verify if everything works fine. When click the delete button should show this window.
After that, you should see.
12. Github Repo.
This is the link to the repo. Laravel Livewire Crud Repo
Happy coding โ. Thanks for reading ๐.