En este tutorial aprenderás cómo crear un CRUD en Laravel 9 y Vue 3 usando el gestor de frontend Vite el cual es el sustituto de WebPack, ahora Laravel desde la versión 9.19.0 integra Vite de forma nativa. Sin más, manos a la obra.
Desde la terminal de comandos ve a la raíz de tu entorno de desarrollo y ejecuta la instrucción:
composer create-project laravel/laravel crud-laravel-9-vue-3-vite
cd crud-laravel-9-vue-3-vite
Instala el sistema de autenticación Laravel Breeze corriendo en la terminal uno a uno los siguientes comandos:
composer require laravel/breeze --dev
php artisan breeze:install
php artisan breeze:install vue
npm install && npm run dev
Abre otra terminal en la raíz de tu proyecto y enciende el servidor integrado de artisan:
php artisan serve
En tu navegador web ve a la dirección http://127.0.0.1:8000/ verás la pantalla de inicio de Laravel con autenticación:
Para crear una nueva base de datos para tu proyecto abre la consola de MySQL escribiendo en la terminal la siguiente instrucción e ingresando tus credenciales:
mysql -u root -p
Crea una base de datos nueva:
CREATE DATABASE crud_laravel_vue_vite CHARACTER SET utf8 COLLATE utf8_spanish_ci; exit
Configura la conexión a la nueva base de datos abriendo con tu editor de textos el archivo .env que se encuentra en la raíz del proyecto y agrega los datos:
Para este tutorial crearé un modelo llamado Book con su migración y controlador para ello ejecuta en la terminal la siguiente instrucción:
php artisan make:model Book -mcr
Con tu editor de textos abre el archivo database/migrations/…create_books_table.php y agrega los campos ‘title’, ‘autor’ y ‘review’, la tabla debe quedar así:
Schema::create('books', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('autor'); $table->text('review'); $table->timestamps(); });
Para correr las migraciones en la terminal de comandos ejecuta el comando:
php artisan migrate
Abre el archivo app/Models/Book y para habilitar la asignación masiva agrega la siguiente línea de código:
protected $fillable = ['title', 'autor', 'review'];
Abre el archivo app/Http/Controllers/BookController.php y en cada método agrega su respectivo código:
Al inicio del archivo importa la clase Inertia:
use Inertia\Inertia;
public function index() { $books = Book::all(); return Inertia::render( 'Books/Index', [ 'books' => $books ] ); }
public function create() { return Inertia::render( 'Books/Create' ); }
public function store(Request $request) { $request->validate([ 'title' => 'required', 'autor' => 'required', 'review' => 'required' ]); Book::create([ 'title' => $request->title, 'autor' => $request->autor, 'review' => $request->review ]); sleep(1); return redirect()->route('books.index')->with('message', 'Book Created Succesfully'); }
public function edit(Book $book) { return Inertia::render( 'Books/Edit', [ 'book' => $book ] ); }
public function update(Request $request, Book $book) { $request->validate([ 'title' => 'required', 'autor' => 'required', 'review' => 'required' ]); $book->title = $request->title; $book->autor = $request->autor; $book->review = $request->review; $book->save(); sleep(1); return redirect()->route('books.index')->with('message', 'Book Updated Successfully'); }
public function destroy(Book $book) { $book->delete(); sleep(1); return redirect()->route('books.index')->with('message', 'Book Delete Successfully'); }
Con tu editor de código abre el archivo routes/web.php y en el inicio del archivo importa el controlador BookController:
use App\Http\Controllers\BookController;
Para crear las rutas agrega la siguiente línea de código:
Route::resource('books', BookController::class);
Con tu editor de texto crea la carpeta resources/js/Pages/Books y dentro de ella vas a crear los archivos que serán las vistas en Vue: Index.vue, Create.vue y Edit.vue.
<script setup> import BreezeAuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue"; import { Head } from "@inertiajs/inertia-vue3"; import BreezeButton from "@/Components/PrimaryButton.vue"; import { Link } from "@inertiajs/inertia-vue3"; import { Inertia } from "@inertiajs/inertia"; import { useForm } from '@inertiajs/inertia-vue3' const props = defineProps({ books: { type: Object, default: () => ({}), }, }); const form = useForm(); function destroy(id) { if (confirm("Are you sure you want to Delete")) { form.delete(route('books.destroy', id)); } } </script> <template> <Head title="Books" /> <BreezeAuthenticatedLayout> <template #header> <h2 class="text-xl font-semibold leading-tight text-gray-800"> Books Index </h2> </template> <div class="py-12"> <div class="mx-auto max-w-7xl sm:px-6 lg:px-8"> <!-- <div v-if="$page.props.flash.message" class="p-4 mb-4 text-sm text-green-700 bg-green-100 rounded-lg dark:bg-green-200 dark:text-green-800" role="alert" > <span class="font-medium"> {{ $page.props.flash.message }} </span> </div> --> <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> <div class="mb-2"> <Link :href="route('books.create')"> <BreezeButton>Add Book</BreezeButton></Link > </div> <div class="relative overflow-x-auto shadow-md sm:rounded-lg" > <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400" > <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400" > <tr> <th scope="col" class="px-6 py-3">#</th> <th scope="col" class="px-6 py-3"> Title </th> <th scope="col" class="px-6 py-3"> Autor </th> <th scope="col" class="px-6 py-3"> Edit </th> <th scope="col" class="px-6 py-3"> Delete </th> </tr> </thead> <tbody> <tr v-for="book in books" :key="book.id" class="bg-white border-b dark:bg-gray-800 dark:border-gray-700" > <th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap" > {{ book.id }} </th> <th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap" > {{ book.title }} </th> <td class="px-6 py-4"> {{ book.autor }} </td> <td class="px-6 py-4"> <Link :href=" route( 'books.edit', book.id ) " class="px-4 py-2 text-white bg-blue-600 rounded-lg" >Edit</Link > </td> <td class="px-6 py-4"> <BreezeButton class="bg-red-700" @click="destroy(book.id)" > Delete </BreezeButton> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div> </BreezeAuthenticatedLayout> </template>
<script setup> import BreezeAuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue"; import { Head } from "@inertiajs/inertia-vue3"; import BreezeButton from "@/Components/PrimaryButton.vue"; import { Link } from "@inertiajs/inertia-vue3"; import { useForm } from "@inertiajs/inertia-vue3"; const props = defineProps({ books: { type: Object, default: () => ({}), }, }); const form = useForm({ title: '', autor: '', review: '', }); const submit = () => { form.post(route("books.store")); }; </script> <template> <Head title="Book Create" /> <BreezeAuthenticatedLayout> <template #header> <h2 class="text-xl font-semibold leading-tight text-gray-800"> Book Create </h2> </template> <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"> <form @submit.prevent="submit"> <div class="mb-6"> <label for="Title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Title</label > <input type="text" v-model="form.title" name="title" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="" /> <div v-if="form.errors.title" class="text-sm text-red-600" > {{ form.errors.title }} </div> </div> <div class="mb-6"> <label for="Autor" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Autor</label > <input type="text" v-model="form.autor" name="autor" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="" /> <div v-if="form.errors.autor" class="text-sm text-red-600" > {{ form.errors.autor }} </div> </div> <div class="mb-6"> <label for="review" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Review</label > <textarea type="text" v-model="form.review" name="review" id="" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" ></textarea> <div v-if="form.errors.review" class="text-sm text-red-600" > {{ form.errors.review }} </div> </div> <button type="submit" class="text-white bg-blue-700 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 " :disabled="form.processing" :class="{ 'opacity-25': form.processing }" > Submit </button> </form> </div> </div> </div> </div> </BreezeAuthenticatedLayout> </template>
<script setup> import BreezeAuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue"; import { Head } from "@inertiajs/inertia-vue3"; import BreezeButton from "@/Components/PrimaryButton.vue"; import { Link } from "@inertiajs/inertia-vue3"; import { useForm } from "@inertiajs/inertia-vue3"; const props = defineProps({ book: { type: Object, default: () => ({}), }, }); const form = useForm({ id: props.book.id, title: props.book.title, autor: props.book.autor, review: props.book.review, }); const submit = () => { form.put(route("books.update", props.book.id)); }; </script> <template> <Head title="Book Edit" /> <BreezeAuthenticatedLayout> <template #header> <h2 class="text-xl font-semibold leading-tight text-gray-800"> Book Edit </h2> </template> <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"> <form @submit.prevent="submit"> <div class="mb-6"> <label for="Title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Title</label > <input type="text" v-model="form.title" name="title" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="" /> <div v-if="form.errors.title" class="text-sm text-red-600" > {{ form.errors.title }} </div> </div> <div class="mb-6"> <label for="Autor" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Autor</label > <input type="text" v-model="form.autor" name="autor" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="" /> <div v-if="form.errors.autor" class="text-sm text-red-600" > {{ form.errors.autor }} </div> </div> <div class="mb-6"> <label for="review" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300" >Review</label > <textarea type="text" v-model="form.review" name="review" id="" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" ></textarea> <div v-if="form.errors.review" class="text-sm text-red-600" > {{ form.errors.review }} </div> </div> <button type="submit" class="text-white bg-blue-700 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 " :disabled="form.processing" :class="{ 'opacity-25': form.processing }" > Submit </button> </form> </div> </div> </div> </div> </BreezeAuthenticatedLayout> </template>
Abre el archivo resources/js/Layouts/AuthenticatedLayout.vue busca la sección ‘Navigation Links’ y agrega el siguiente trozo de código:
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"> <NavLink :href="route('books.index')" :active="route().current('books.index')"> Books </NavLink> </div>
Para probar tu CRUD en Laravel 9 y Vue 3 usando Vite, tienes que abrir dos terminales de comando en la raíz del proyecto. En la primer terminal corre el comando:
npm i && npm run dev
En la segunda terminal ejecuta la instrucción:
php artisan serve
Ahora en el navegador web ve a la dirección http://localhost:8000/ y registrate, cuando ingreses en el Dashboard ve al menú Books:
Ya puedes hacer uso del CRUD, puedes agregar libros, editarlos y borrarlos.
En este tutorial aprendiste a crear un CRUD en Laravel 9 y Vue 3 usando Vite, estas bases te pueden servir para futuros proyectos. Si te ha servido el tutorial te invito a compartirlo en tus redes sociales para llegar a más personas y si tienes dudas o comentarios déjalos en la caja de comentarios, estaré al pendiente de ellos. Saludos!
Repositorio GitHub del tutorial.
Te puede interesar: CRUD con Laravel 9 y React.
Referencias:
Vite se integra de forma nativa a Laravel.
Vite documentación.
El lanzamiento de Laravel 11 está muy próximo ya que está programado para el tercer…
En este tutorial aprenderás una solución para cuando WordPress no envía correos y esto puede…
En este tutorial aprenderás cómo configurar distintos Virtual Host en Ubuntu Desktop y de esta…
En este tutorial aprenderás cómo instalar stack LAMP en Linux Mint fácilmente y de esta…
En este tutorial aprenderás paso a paso cómo crear una USB multiboot con distintos OS…
El desarrollo de temas y plugins para WordPress es un nicho muy específico en el…
Ver comentarios
¡Genial! Acabo de implementarlo y funciona bien.
Hola Samuel, gracias por visitar y comentar, me alegra saber que este tutorial fue de ayuda, te envío un saludo!
Hola , segui todos los pasos , al momento de dar clic en Books me mando el siguiente error plugin:vite:import-analysis] Failed to resolve import "@inertiajs/inertia-vue3" from "resources/js ...
volvi hacer mis instalaciones, pero ahora ya no veo el submenu Books y en lugar del botón veo You're logged in!
Agradezco su ayuda
Hola Ana, gracias por visitar y comentar. Si te muestra un error puedes ver el código en el repositorio de GitHub https://github.com/diarioprogramador/crud-laravel-9-vue-3-vite puedes usarlo o compararlo con el tuyo, espero que sea de ayuda, si necesitas ayuda puedes contactarme mandando un mensaje desde el formulario de contacto.Te mando un saludo!
Muy bueno! Segui todos los pasos y me funciono correctamente, muchas gracias!
Hola Rene, gracias por visitar y comentar. Me alegra saber que este tutorial fue de ayuda. Saludos!
Hola que tal, me sale este error revise todos los pasos, que podría ser?
Hola Ricardo, gracias por visitar y comentar. El problema dice que no encuentra el archivo en la ruta proporcionada, revisa si el archivo esta al mismo nivel o si tienes que subir un nivel al llamarlo. Te dejo el link al repositorio para que compares tu código https://github.com/diarioprogramador/crud-laravel-9-vue-3-vite o si necesitas más ayuda puedes compartirme tu repositorio para analizarlo. Saludos!
[plugin:vite:import-analysis] Failed to resolve import "@inertiajs/inertia-vue3" from "resources\js\Pages\Books\Index.vue". Does the file exist?