CRUD Laravel 9 Vue 3 usando Vite
Imagen de 8212733 en Pixabay

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.

Requisitos previos

  1.  Entorno de desarrollo como XAMPP, Wamp o Laragon.
  2.  Composer instalado de manera global en el SO.
  3.  Contar con Node.

1. Crear un nuevo proyecto Laravel 9 y Vue 3

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

2. Instalar Laravel Breeze

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:

3. Crear y configurar base de datos

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:

CRUD Laravel 9 Vue 3 usando Vite

4. Crear modelo, migración y controlador

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

5. Configurar archivo de migración

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

6. Configuración archivo de modelo

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'];

7. Configurar los métodos del controlador

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;

7.1. Método index

public function index()
{
   $books = Book::all();

   return Inertia::render(
      'Books/Index',
      [
         'books' => $books
      ]
   );
}

7.2. Método create

public function create()
{
   return Inertia::render(
      'Books/Create'
   );
}

7.3. Método store

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');

}

7.4. Método edit

public function edit(Book $book)
{
   return Inertia::render(
      'Books/Edit',
      [
         'book' => $book
      ]
   );
}

7.5. Método update

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');
}

7.6. Método destroy

public function destroy(Book $book)
{
   $book->delete();
   sleep(1);

   return redirect()->route('books.index')->with('message', 'Book Delete Successfully');
}

8. Crear rutas

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);

9. Crear archivos de Vue 3

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.

9.1. Configurar Index.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>

9.2. Configurar Create.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 { 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>

 

9.3. Configurar 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 { 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>

9.4. Agregar elemento al menú

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>

CRUD Laravel 9 Vue 3 usando Vite

10. Probar CRUD en Laravel 9 y Vue 3

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:

Ve al menu Books

Ya puedes hacer uso del CRUD, puedes agregar libros, editarlos y borrarlos.

Conclusión

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.

9 COMENTARIOS

  1. 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

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.