En este tutorial aprenderás a crear un CRUD en Laravel Livewire de forma completa haciendo uso de las herramientas más típicas en el desarrollo de aplicaciones web con Laravel.
Si tienes dudas acerca de cómo crear un CRUD en Laravel, no te pierdas esta entrada, tienes un paso a paso completo desde cero incluido el proyecto final para que lo puedas descargar.
Proyecto final
Descarga el proyecto final desde GitHub.
Crear proyecto Laravel Livewire con Jetstream
Lo primero que debemos hacer para desarrollar un CRUD en Laravel Livewire es crear nuestro proyecto, en nuestro caso lo haremos con el instalador de Laravel haciendo uso del flag --jet, con eso añadiremos Jetstream a nuestro proyecto y podremos instalar y configurar de forma automática Livewire en nuestro proyecto.
laravel new crud-laravel-livewire --jet
cd crud-laravel-livewireCon los comandos anteriores ya tendremos nuestro proyecto Laravel creado y Laravel Livewire instalado y listo para funcionar.
Modelo y migración
Ahora vamos a crear nuestro modelo y nuestra migración con el siguiente comando, el cual también creará el controlador con el que gestionaremos más adelante los proyectos de nuestro CRUD.
php artisan make:model Project -mcVamos a abrir nuestra migración para modificar el código por lo siguiente:
<?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('projects', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(\App\Models\User::class)->constrained();
$table->string('name', 100)->unique();
$table->string('description', 255);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('projects');
}
};Ahora vamos a abrir nuestro modelo Project para hacer algunos ajustes:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Project extends Model
{
protected $fillable = [
'name',
'description',
];
protected static function boot()
{
parent::boot();
static::creating(function ($project) {
$project->user_id = auth()->id();
});
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}Con todo lo anterior, vamos a generar nuestra base de datos con la tabla de proyectos, pero recuerda primero actualizar las credenciales de tu base de datos en el archivo .env:
php artisan migrate:freshRutas CRUD en Laravel Livewire
Vamos a actualizar nuestro archivo de rutas web.php para añadir una ruta resource que nos permita llevar a cabo todas las operaciones de CRUD:
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified'
])->group(function () {
// ...
Route::resource('projects', ProjectController::class)
->only('index', 'create', 'show', 'edit');
});El método resource generará los siguientes endpoints en nuestra aplicación:
GET|HEAD projects ......................................................... projects.index › ProjectController@index
GET|HEAD projects/create .................................................. projects.create › ProjectController@create
GET|HEAD projects/{project} ............................................... projects.show › ProjectController@show
GET|HEAD projects/{project}/edit .......................................... projects.edit › ProjectController@editCRUD Laravel Livewire: Listar proyectos con paginación
Vamos a añadir el método index a nuestro controlador ProjectController para renderizar más adelante un componente Livewire que muestre los proyectos paginados:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Support\Renderable;
use App\Models\Project;
class ProjectController extends Controller
{
public function index(): Renderable
{
return view('projects.index');
}
}Ahora, vamos a crear el componente Livewire Paginated para nuestro listado de proyectos:
php artisan make:livewire Project\\PaginatedNuestro componente Paginated simplemente obtendrá los proyectos y los paginará en un listado:
<?php
namespace App\Http\Livewire\Project;
use Illuminate\Contracts\Support\Renderable;
use Livewire\Component;
use App\Models\Project;
use Livewire\WithPagination;
class Paginated extends Component
{
use WithPagination;
public function delete(Project $project): void
{
$project->delete();
}
public function render(): Renderable
{
$projects = Project::latest()->paginate();
return view('livewire.project.paginated', compact('projects'));
}
}La parte más importante de nuestro componente y la única diferencia con un controlador Laravel es el uso del trait WithPagination.
Ahora, vamos a actualizar la vista vinculada a nuestro componente:
<div class="p-6 bg-white border-b border-gray-200">
<div class="flex justify-between">
<h1 class="text-2xl font-bold">{{ __('Proyectos') }}</h1>
<a href="{{ route('projects.create') }}" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">Crear proyecto</a>
</div>
<div class="mt-4">
<table class="table-auto w-full">
<thead class="text-xs font-semibold uppercase text-gray-400 bg-gray-50">
<tr>
<th class="px-4 py-2">{{ __('Nombre') }}</th>
<th class="px-4 py-2">{{ __('Descripción') }}</th>
<th class="px-4 py-2">{{ __('Acciones') }}</th>
</tr>
</thead>
<tbody class="text-sm divide-y divide-gray-100">
@forelse ($projects as $project)
<tr>
<td class="border px-4 py-2">{{ $project->name }}</td>
<td class="border px-4 py-2">{{ $project->description }}</td>
<td class="border px-4 py-4" style="width: 260px">
<a href="{{ route('projects.show', $project) }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">{{ __('Ver') }}</a>
<a href="{{ route('projects.edit', $project) }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">{{ __('Editar') }}</a>
<a role="button" wire:click.prevent="delete('{{ $project->id }}')" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">{{ __('Eliminar') }}</a>
</td>
</tr>
@empty
<tr class="bg-red-400 text-white text-center">
<td colspan="3" class="border px-4 py-2">{{ __('No hay proyectos para mostrar') }}</td>
</tr>
@endforelse
</tbody>
@if ($projects->hasPages())
<tfoot class="text-xs font-semibold uppercase text-gray-400 bg-gray-50">
<tr>
<td colspan="3" class="border px-4 py-2">
{{ $projects->links() }}
</td>
</tr>
</tfoot>
@endif
</table>
</div>
</div>La parte más importante del listado es ver cómo el enlace que elimina un proyecto al pulsarlo utiliza wire:click.prevent="delete('{{ $project->id }}')", con eso podremos eliminar un proyecto llamando al método delete de nuestro componente Livewire.
En este punto, sólo nos queda actualizar la vista index.blade.php vinculada al método index de nuestro controlador para decirle que utilice nuestro componente Livewire:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Proyectos') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<livewire:project.paginated />
</div>
</div>
</div>
</x-app-layout>CRUD Laravel Livewire: Crear proyectos
Vamos a añadir el método create a nuestro controlador ProjectController para mostrar un formulario que nos permita crear nuevos proyectos, veremos que tenemos algunas diferencias con respecto a utilizar plantillas Blade:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Support\Renderable;
use App\Models\Project;
class ProjectController extends Controller
{
// ...
public function create(): Renderable
{
$project = new Project;
$title = __('Crear proyecto');
$buttonText = __('Crear proyecto');
return view('projects.form', compact('project', 'title', 'buttonText'));
}
}Ahora vamos a crear el componente Livewire Form, el cual representará el formulario de alta y edición:
php artisan make:livewire Project\\FormComo he dicho, nuestro componente servirá para crear y editar, pero será muy sencillo:
<?php
namespace App\Http\Livewire\Project;
use App\Models\Project;
use Illuminate\Contracts\Support\Renderable;
use Livewire\Component;
use Livewire\Redirector;
class Form extends Component
{
public Project $project;
public string $buttonText;
public function rules(): array
{
return [
'project.name' => 'required|string|max:255|unique:projects,name,' . request()->route('project'),
'project.description' => 'required|string|max:1000',
];
}
public function messages(): array
{
return [
'project.name.required' => __('El campo nombre es obligatorio.'),
'project.name.string' => __('El campo nombre debe ser una cadena de texto.'),
'project.name.max' => __('El campo nombre no debe ser mayor a :max caracteres.'),
'project.name.unique' => __('El campo nombre ya está en uso.'),
'project.description.required' => __('El campo descripción es obligatorio.'),
'project.description.string' => __('El campo descripción debe ser una cadena de texto.'),
'project.description.max' => __('El campo descripción no debe ser mayor a :max caracteres.'),
];
}
public function save(): Redirector
{
$this->validate();
$this->project->save();
return redirect()->to(route("projects.index"));
}
public function render(): Renderable
{
return view('livewire.project.form');
}
}Sólo necesitamos recibir en forma de props el proyecto y el texto del botón. El proyecto será una instancia del modelo Project, y nos servirá para representarlo en el formulario y también para crear o actualizar su información.
método rules: definimos las reglas de validación, las cuales servirán para crear y actualizar.
método messages: definimos los mensajes de error de la validación para cada campo/regla.
método save: creará o actualizará un proyecto cuando el formulario sea procesado y validado.
Fíjate que tanto en el método rules como en el método messages siempre accedemos al proyecto con project.*.
Ahora vamos a abrir el archivo form.blade.php de nuestro componente Livewire para añadir el siguiente código:
<div class="p-6 bg-white border-b border-gray-200">
<form wire:submit.prevent="save" method="POST">
<div class="mb-4">
<label for="name" class="block text-gray-700 text-sm font-bold mb-2">{{ __('Nombre') }}</label>
<input
type="text"
name="name"
id="name"
wire:model="project.name"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
/>
@error('project.name')
<p class="text-red-500 text-xs italic">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label for="description" class="block text-gray-700 text-sm font-bold mb-2">{{ __('Descripción') }}</label>
<textarea
name="description"
id="description"
wire:model="project.description"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
></textarea>
@error('project.description')
<p class="text-red-500 text-xs italic">{{ $message }}</p>
@enderror
</div>
<div class="flex justify-end">
<a href="{{ route('projects.index') }}" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">{{ __('Cancelar') }}</a>
<button type="submit" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded ml-2">{{ $buttonText }}</button>
</div>
</form>
</div>Ya tenemos nuestro formulario de alta y edición en Laravel Livewire, ahora actualicemos la vista form.blade.php que renderiza el método create de nuestro controlador.
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ $title }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<livewire:project.form
:project="$project"
:button-text="$buttonText"
/>
</div>
</div>
</div>
</x-app-layout>CRUD Laravel Livewire: Editar proyectos
Vamos a añadir el método edit a nuestro controlador ProjectController para mostrar el componente Form de Livewire, el cual nos permitirá editar proyectos existentes:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Support\Renderable;
use App\Models\Project;
class ProjectController extends Controller
{
// ...
public function edit(Project $project): Renderable
{
$title = __('Editar proyecto');
$buttonText = __('Actualizar proyecto');
return view('projects.form', compact('project', 'title', 'buttonText'));
}
}No tenemos que hacer nada más, con la configuración que hemos llevado a cabo anteriormente nuestro componente Livewire sabrá cómo redenrizar los datos del formulario, validar y actualizar sus datos.
CRUD Laravel Livewire: Detalle de un proyecto
Empecemos por añadir el método show a nuestro controlador ProjectController para mostrar el componente Detail de Livewire, el cual nos permitirá más adelante ver el detalle de un proyecto:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Support\Renderable;
use App\Models\Project;
class ProjectController extends Controller
{
// ...
public function show(Project $project): Renderable
{
$project->load('user:id,name');
return view('projects.show', compact('project'));
}
}Vamos a crear el componente Livewire Detail, el cual representará el detalle de un proyecto:
php artisan make:livewire Project\\DetailAhora, vamos a actualizar el componente Detail de Livewire para poder llevar a cabo la lógica necesaria para mostrar el detalle de un proyecto:
<?php
namespace App\Http\Livewire\Project;
use App\Models\Project;
use Illuminate\Contracts\Support\Renderable;
use Livewire\Component;
use Livewire\Redirector;
class Detail extends Component
{
public Project $project;
public function delete(): Redirector
{
$this->project->delete();
return redirect()->to(route("projects.index"));
}
public function render(): Renderable
{
return view('livewire.project.detail');
}
}Como puedes ver, tenemos que pasarle un proyecto en forma de prop, que será el proyecto que mostraremos en el detalle, además, también tenemos un método delete que nos permitirá eliminar el proyecto en cuestión.
Abre la vista detail.blade.php y actualiza el código por el siguiente:
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<h1 class="text-2xl font-bold mb-4">{{ $project->name }}</h1>
<p class="text-gray-700 mb-4">
{{ __('Autor') }}: {{ $project->user->name }}
</p>
<p class="text-gray-700 mb-4">{{ $project->description }}</p>
<p class="text-gray-700">{{ $project->created_at->diffForHumans() }}</p>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 mt-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('projects.index') }}" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded ml-2">{{ __('Volver') }}</a>
<a href="{{ route('projects.edit', $project) }}" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">{{ __('Editar') }}</a>
<a role="button" wire:click="delete" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">{{ __('Eliminar') }}</a>
</div>
</div>
</div>
</div>Para terminar nuestro CRUD en Laravel Livewire, vamos a abrir el archivo que renderiza el método show de nuestro controlador para utilizar nuestro nuevo componente Livewire:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Detalle del proyecto') }}
</h2>
</x-slot>
<livewire:project.detail :project="$project" />
</x-app-layout>Hasta aquí hemos cubierto cómo crear un CRUD en Laravel Livewire, si quieres seguir aprendiendo a trabajar con Laravel Livewire, no te pierdas nuestros cursos:
Aplicación de recordatorios con Laravel: Jobs, Queues, Notificaciones y más
Formularios en Laravel, aprende a validar formularios complejos
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción