• Saltar a la navegación principal
  • Saltar al contenido principal

JAVIER GUTIÉRREZ

Desarrollador web

  • Newsletter
  • Blog
  • Acerca de mí

CRUD con Jetstream y Twailwind (Laravel 8 Inertia)

Una de las nuevas características que incluye Laravel 8 es el scaffolding Jetstream, el cual proporciona un punto de inicio para nuestra aplicación que incluye funcionalidades como el login, registro o autenticación. Jetstream está diseñado con Tailwind CSS y nos da la oportunidad de usar el stack de Livewire (con el sistema de plantillas Blade) o Inertia (usando componentes de Vue). Jetstream viene a ser una mejora del scaffolding Laravel UI.

Vamos a seguir una guía paso a paso para crear una SPA (sin la necesidad de construir una API), la cual va a realizar operaciones CRUD sobre una base de datos utilizando el stack Inertia .

La aplicación que vamos a desarrollar nos permite añadir un producto con el nombre y el precio, así como borrarlo o editar uno ya existente. todo ello en el caso de habernos registrado previamente con el correo electrónico.

Los Templates de los componente están maquetados con el Framework Tailwind CSS, y aunque en los ejemplos he prescindido de las clases de css para mayor claridad del código, este se encuentra en su versión completa en el siguiente repositorio de Github: gutifer666/Inertia-Dickinson.

Tabla de contenidos:

  • Paso 1: Instalar Laravel 8 e Inertia js
    • Paso 2: Conectar Laravel con la Base de Datos
      • Paso 3: Crear Modelo y hacer Migración en Laravel
        • Paso 4: Crear un controlador con recursos con las operaciones CRUD
          • Paso 5: Implementar las funciones del CRUD
            • Listado de Productos
              • Añadir Producto
                • Editar un Producto
                  • Eliminar un Producto
                  • Paso 6: Crear rutas y protegerlas con Laravel Sanctum
                    • Paso 7: Modificar Layout y crear Páginas con Laravel Inertia
                      • Modificar Layout en /resources/js/Layouts/AppLayout.vue:
                        • Crear páginas en resources/js/Pages
                          • Página List
                            • Página Form
                              • Página EditForm
                            • Puede que te interese :

                              Paso 1: Instalar Laravel 8 e Inertia js

                              Para instalar Inertia sólo necesitamos abrir el terminal ejecutando el siguiente comando y eligiendo la opción de Inertia JS cuando nos pregunte el instalador:

                              Shell
                               
                              laravel new product-stock-inertia --jet

                              Durante la instalación se ejecutan los comandos npm install && npm run dev lo cual nos crea los archivos app.css y app.js ya compilados ( para desarrollo ).

                              Paso 2: Conectar Laravel con la Base de Datos

                              Creamos la base de datos product_stock_inertia y editamos el fichero .env como sigue:

                              Shell
                              /.env
                              xxxxxxxxxx
                               
                              DB_CONNECTION=mysql
                              DB_HOST=127.0.0.1
                              DB_PORT=3306
                              DB_DATABASE=product_stock_inertia
                              DB_USERNAME=root
                              DB_PASSWORD=

                              Y ejecutamos las migraciones para que se creen las tablas que incorpora Inertia:

                              Shell
                              xxxxxxxxxx
                               
                              php artisan migrate

                              Paso 3: Crear Modelo y hacer Migración en Laravel

                              Al ejecutar el siguiente comando se va a crear el modelo Product y su correspondiente tabla de migración:

                              Shell
                              xxxxxxxxxx
                               
                              php artisan make:model Product -m

                              A continuación añadiremos las tablas name y price modificando la función up ( ) del siguiente archivo /database/migrations/create_product_table.php:

                              PHP
                              /database/migrations/create_products_table.php
                              xxxxxxxxxx
                               
                              <?php
                              public function up()
                              {
                                  Schema::create('product', function (Blueprint $table) {
                                      $table->id();
                                      $table->string('name'); //Añadir
                                      $table->text('price'); // Añadir
                                      $table->timestamps();
                                  });
                              }

                              Para finalizar con el modelo otorgamos la propiedad fillable a los atributos que permitimos ser manipulados desde el exterior:

                              PHP
                              app/models/Product.php
                              xxxxxxxxxx
                               
                              <?php
                               
                              namespace App\Models;
                               
                              use Illuminate\Database\Eloquent\Factories\HasFactory;
                              use Illuminate\Database\Eloquent\Model;
                               
                              class Product extends Model
                              {
                                  use HasFactory;
                               
                                  protected $fillable = [
                                      'name', 'price',
                                  ];
                              }

                              Y finalmente ejecutamos la migración desde artisan para crear las tablas del modelo en la base de datos:

                              Shell
                              xxxxxxxxxx
                               
                              php artisan migrate

                              Paso 4: Crear un controlador con recursos con las operaciones CRUD

                              La siguiente línea crea el Controlador del Modelo Product y genera el esqueleto de las funciones que vamos a necesitar para implementar el CRUD:

                              Shell
                              xxxxxxxxxx
                               
                              php artisan make:controller ProductController --resource --model=Product

                              El controlador quedaría ubicado en app/http/controllers:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              ​x
                               
                              <?php
                              ​
                              class ProductController extends Controller
                              {
                                  public function index()
                                  {
                                      //
                                  }
                                  public function create()
                                  {
                                      //
                                  }
                                  public function store(Request $request)
                                  {
                                      //
                                  }
                                  public function show(Product $product)
                                  {
                                      // NO LA VAMOS A UTILIZAR
                                  }
                                  public function edit(Product $product)
                                  {
                                      //
                                  }
                                  public function update(Request $request, Product $product)
                                  {
                                      //
                                  }
                                  public function destroy(Product $product)
                                  {
                                      //
                                  }
                              }

                              Paso 5: Implementar las funciones del CRUD

                              Estas son las clases en las que vamos a apoyarnos:

                              PHP
                              xxxxxxxxxx
                               
                              <?php
                              ​
                              namespace App\Http\Controllers;
                              ​
                              use Illuminate\Support\Facades\Redirect;
                              use Inertia\Inertia;
                              use App\Models\Product;
                              use Illuminate\Http\Request;

                              Listado de Productos

                              Renderiza la página ‘List‘ pasándole los productos como parámetro:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function index()
                                  {
                                      $products = Product::all();
                                      return Inertia::render('List', ['products' => $products]);
                                  }

                              Añadir Producto

                              Muestra el formulario para crear un nuevo producto:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function create()
                                  {
                                      return Inertia::render('Form');
                                  }

                              Almacena el nuevo producto creado validando previamente la entrada de datos y redirigiéndonos posteriormente al listado de productos:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function store(Request $request)
                                  {
                                      $request->validate(
                                          [
                                              'name' => 'required',
                                              'price' => 'required'
                                          ]
                                      );
                              ​
                                      Product::create($request->all());
                              ​
                                      return Redirect::route('products.index');
                                  }

                              Editar un Producto

                              Muestra el formulario para editar un producto específico pasado por parámetro:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function edit(Product $product)
                                  {
                                      return Inertia::render('EditForm', ['product' => $product]);
                                  }

                              Actualiza el producto específico, el cual pasamos por parámetro y devolvemos el listado de productos nuevamente:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function update(Request $request, Product $product)
                                  {
                                      $product->update($request->all());
                                      return Redirect::route('products.index');
                                  }

                              Eliminar un Producto

                              Eliminamos el producto especificado por el parámetro y retornamos a la página del listado:

                              PHP
                              /app/Http/Controllers/ProductController.php
                              xxxxxxxxxx
                               
                              <?php
                                  public function destroy(Product $product)
                                  {
                                      $product->delete();
                                      return Redirect::route('products.index');
                                  }

                              Paso 6: Crear rutas y protegerlas con Laravel Sanctum

                              Registramos la ruta de la raíz, la cual nos retorna la vista de bienvenida y las rutas de los recursos de productos redirigidos al controlador. Estas últimas rutas están protegidas mediante Middleware y Laravel Sanctum, siendo necesario estar autenticado para acceder a ellas:

                              PHP
                              /routes/web.app
                              xxxxxxxxxx
                               
                              <?php
                              ​
                              use App\Http\Controllers\ProductController;
                              use Illuminate\Support\Facades\Route;
                              ​
                              Route::get('/', function () {
                                  return view('welcome');
                              });
                              Route::resource('products', ProductController::class)
                                  ->middleware(['auth:sanctum', 'verified']);
                              ​

                              Si ejecutamos la siguiente línea de comando:

                              Shell
                              xxxxxxxxxx
                               
                              php artisan route:list

                              Obtendremos información sobre las rutas creadas:

                              Shell
                              xxxxxxxxxx
                               
                              Verb        URI                    Action       Route Name
                              ​
                              GET        /products               index       products.index
                              GET        /products/create        create     products.create
                              POST       /products               store       products.store
                              GET        /products/{product}      show      products.show
                              GET        /products/{product}/edit edit       products.edit
                              PUT/PATCH /products/{product}     update     products.update
                              DELETE     /products/{product}    destroy    products.destroy

                              Paso 7: Modificar Layout y crear Páginas con Laravel Inertia

                              Vamos a modificar el Layout que viene por defecto con Inertia JS y a crear las páginas de formularios y listado para nuestra aplicación.

                              Modificar Layout en /resources/js/Layouts/AppLayout.vue:

                              Implementamos el menú de navegación haciendo uso del componente NavLink.vue del cual nos provee Jetstream y que a su vez hace uso del componente <inertia-link>:

                              JS
                              /resources/js/Layouts/AppLayout.vue
                              xxxxxxxxxx
                              30
                               
                              15
                                          <!-- Navigation Links -->
                              16
                                          <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                              17
                                            <jet-nav-link
                              18
                                              :href="route('products.index')"
                              19
                                              :active="route().current('products.index')"
                              20
                                            >
                              21
                                              Productos
                              22
                                            </jet-nav-link>
                              23
                                            <jet-nav-link
                              24
                                              :href="route('products.create')"
                              25
                                              :active="route().current('products.create')"
                              26
                                            >
                              27
                                              Añadir
                              28
                                            </jet-nav-link>
                              29
                                          </div>
                              30
                                        </div>

                              De manera similar creamos el menú Responsive. esta vez haciendo uso del componente ResponsiveNavLink.vue:

                              JS
                              /resources/js/Layouts/AppLayout.vue
                              xxxxxxxxxx
                              211
                               
                              190
                                    <!-- Responsive Navigation Menu -->
                              191
                                    <div
                              192
                                      :class="{
                              193
                                        block: showingNavigationDropdown,
                              194
                                        hidden: !showingNavigationDropdown,
                              195
                                      }"
                              196
                                      class="sm:hidden"
                              197
                                    >
                              198
                                      <div class="pt-2 pb-3 space-y-1">
                              199
                                        <jet-responsive-nav-link
                              200
                                          :href="route('products.index')"
                              201
                                          :active="route().current('products.index')"
                              202
                                        >
                              203
                                          Productos
                              204
                                        </jet-responsive-nav-link>
                              205
                                        <jet-responsive-nav-link
                              206
                                          :href="route('products.create')"
                              207
                                          :active="route().current('products.create')"
                              208
                                        >
                              209
                                          añadir
                              210
                                        </jet-responsive-nav-link>
                              211
                                      </div>

                              El script de nuestro Layout quedaría como sigue:

                              JS
                              /resources/js/Layouts/AppLayout.vue
                              xxxxxxxxxx
                               
                              <script>
                              import JetApplicationMark from "@/Jetstream/ApplicationMark";
                              import JetDropdown from "@/Jetstream/Dropdown";
                              import JetDropdownLink from "@/Jetstream/DropdownLink";
                              import JetNavLink from "@/Jetstream/NavLink";
                              import JetResponsiveNavLink from "@/Jetstream/ResponsiveNavLink";
                              ​
                              export default {
                                components: {
                                  JetApplicationMark,
                                  JetDropdown,
                                  JetDropdownLink,
                                  JetNavLink,
                                  JetResponsiveNavLink,
                                },
                              ​
                                data() {
                                  return {
                                    showingNavigationDropdown: false,
                                  };
                                },
                              ​
                                methods: {
                                  switchToTeam(team) {
                                    this.$inertia.put(
                                      route("current-team.update"),
                                      {
                                        team_id: team.id,
                                      },
                                      {
                                        preserveState: false,
                                      }
                                    );
                                  },
                              ​
                                  logout() {
                                    axios.post(route("logout").url()).then((response) => {
                                      window.location = "/";
                                    });
                                  },
                                },
                              };
                              </script>

                              Crear páginas en resources/js/Pages

                              Página List

                              En el Template del componente List.vue creamos una tabla en la que mostrar el listado de los productos con la ayuda de la directiva v-for de Vue y el uso de la propiedad products. Todo ello envuelto en la etiqueta <app-layout> del componente AppLayout:

                              JS
                              /resources/js/Pages/List.vue
                              xxxxxxxxxx
                               
                              1
                              <template>
                              2
                                <app-layout>
                              3
                                          <table>
                              4
                                            <thead>
                              5
                                              <tr>
                              6
                                                <th>
                              7
                                                  Producto
                              8
                                                </th>
                              9
                                                <th >
                              10
                                                  Precio
                              11
                                                </th>
                              12
                                              </tr>
                              13
                                            </thead>
                              14
                                            <tbody>
                              15
                                              <tr v-for="product in products" :key="product.id">
                              16
                                                <td>
                              17
                                                        {{ product.name }}
                              18
                                                </td>
                              19
                                                <td>
                              20
                                                  <div>{{ product.price }}</div>
                              21
                                                  <div>       Euros       </div>
                              22
                                                </td>
                              23
                                                <td>
                              24
                                                  <inertia-link :href="route('products.edit', product.id)">
                              25
                                                              Editar
                              26
                                                  </inertia-link>
                              27
                                                </td>
                              28
                              ​
                              29
                                                <td>
                              30
                                                  <inertia-link
                              31
                                                    method="delete"
                              32
                                                    :href="route('products.destroy', product.id)">
                              33
                                                    Borrar
                              34
                                                  </inertia-link>
                              35
                                                </td>
                              36
                                              </tr>
                              37
                                            </tbody>
                              38
                                          </table>
                              39
                              ​
                              40
                                </app-layout>
                              41
                              </template>

                              En el script declaramos la propiedad products e importamos el componente AppLayout.vue:

                              JS
                              /resources/js/Pages/List.vue
                              xxxxxxxxxx
                               
                              81
                              <script>
                              82
                              import AppLayout from "@/Layouts/AppLayout";
                              83
                              ​
                              84
                              export default {
                              85
                                props: {
                              86
                                  products: Array,
                              87
                                },
                              88
                                components: {
                              89
                                  AppLayout,
                              90
                                },
                              91
                              };
                              92
                              </script>

                              En el Template del formulario, prevenimos el envío de este y ejecutamos el método «submit«. Recogemos los datos y mostramos un mensaje de error si son incorrectos:

                              Página Form

                              JS
                              /resources/js/Pages/Form.vue
                              xxxxxxxxxx
                               
                              <template>
                                <app-layout>
                                      <form @submit.prevent="submit">
                                        <div>
                                          <label for="name">Nombre:</label >
                                          <input id="name" v-model="form.name" />
                                          <div v-if="errors.name">El Nombre es Requerido</div>
                                        </div>
                                        <div>
                                          <label for="price">Precio:</label>
                                          <input id="price"v-model="form.price"/>
                                          <div v-if="errors.price">El Precio es Requerido</div>
                                        </div>
                                        <button type="submit"> Añadir </button>
                                      </form>
                                    </div>
                                  </div>
                                </app-layout>
                              </template>

                              En el script importamos AppLayout, declaramos la propiedad errors, creamos el objeto form con las propiedades name y price e implementamos el método submit para enviar los datos de form vía post a través de la ruta de almacenamiento:

                              JS
                              /resources/js/Pages/Form.vue
                              xxxxxxxxxx
                               
                              <script>
                              import AppLayout from "@/Layouts/AppLayout";
                              export default {
                                components: {
                                  AppLayout,
                                },
                                props: {
                                  errors: Object,
                                },
                                data() {
                                  return {
                                    form: {
                                      name: null,
                                      price: null,
                                    },
                                  };
                                },
                                methods: {
                                  submit() {
                                    this.$inertia.post(route('products.store'), this.form);
                                  },
                                },
                              };
                              </script>

                              El Template de Editform.vue es similar al de Form.vue:

                              Página EditForm

                              JS
                              /resources/js/Pages/EditForm.vue
                              xxxxxxxxxx
                               
                              <template>
                                <app-layout>
                                      <form @submit.prevent="submit">
                                        <div>
                                          <label for="name">Nombre:</label >
                                          <input id="name" v-model="form.name" />
                                          <div v-if="errors.name">El Nombre es Requerido</div>
                                        </div>
                                        <div>
                                          <label for="price">Precio:</label>
                                          <input id="price"v-model="form.price"/>
                                          <div v-if="errors.price">El Precio es Requerido</div>
                                        </div>
                                        <button type="submit"> Añadir </button>
                                      </form>
                                    </div>
                                  </div>
                                </app-layout>
                              </template>

                              En el script, como propiedad obtenemos el producto específico que queremos editar y los datos de form los inicializamos con los datos del mismo producto. El método submit envía los datos modificados vía put junto con el id del producto a la ruta update:

                              JS
                              /resources/js/Pages/EditForm.vue
                              xxxxxxxxxx
                               
                              <script>
                              import AppLayout from "@/Layouts/AppLayout";
                              export default {
                                components: {
                                  AppLayout,
                                },
                                props: ["product"],
                                data() {
                                  return {
                                    form: {
                                      name: this.$props.product.name,
                                      price: this.$props.product.price,
                                    },
                                  };
                                },
                                methods: {
                                  submit() {
                                    this.$inertia.put(
                                      route("products.update", this.$props.product.id),
                                      this.form
                                    );
                                  },
                                },
                              };
                              </script>

                              Puede que te interese :

                              Entorno de Desarrollo Local Laravel en Ubuntu para Escritorio

                              In Laravel By guti

                              Sobre Ubuntu Desktop 20.04 y lo haremos instalando PHP y algunos módulos, CURL, MySQL, Composer y Laravel Installer. Nos será útil porque ciertas aplicaciones ( Multiinquilino ) no se ejecutan correctamente sobre entornos virtuales( XAMP ) o en contenedores ( Docker ).

                              Read more

                              Archivado en: Laravel

                              Interacciones con los lectores

                              Comentarios

                              1. rodrigo dice

                                12/04/2021 en 13:50

                                Saludos y gracias por el tutorial pero por algúna razón cuando intento entrar al formulario de edición de un producto, me arroja un error que dice lo siguiente:
                                app.js:22261 Uncaught (in promise) TypeError: Cannot read property ‘name’ of undefined
                                at app.js:22261
                                at renderFnWithContext (app.js:6487)
                                at renderSlot (app.js:6405)
                                at Proxy.render (app.js:20819)
                                at renderComponentRoot (app.js:6523)
                                at componentEffect (app.js:9948)
                                at reactiveEffect (app.js:4720)
                                at effect (app.js:4695)
                                at setupRenderEffect (app.js:9931)
                                at mountComponent (app.js:9890)
                                (anonymous) @ app.js:22261
                                renderFnWithContext @ app.js:6487
                                renderSlot @ app.js:6405
                                render @ app.js:20819
                                renderComponentRoot @ app.js:6523
                                componentEffect @ app.js:9948
                                reactiveEffect @ app.js:4720
                                effect @ app.js:4695
                                setupRenderEffect @ app.js:9931
                                mountComponent @ app.js:9890
                                processComponent @ app.js:9850
                                patch @ app.js:9468
                                componentEffect @ app.js:9966
                                reactiveEffect @ app.js:4720
                                effect @ app.js:4695
                                setupRenderEffect @ app.js:9931
                                mountComponent @ app.js:9890
                                processComponent @ app.js:9850
                                patch @ app.js:9468
                                componentEffect @ app.js:10036
                                reactiveEffect @ app.js:4720
                                callWithErrorHandling @ app.js:5834
                                flushJobs @ app.js:6061
                                Promise.then (async)
                                queueFlush @ app.js:5963
                                queueJob @ app.js:5957
                                run @ app.js:4861
                                trigger @ app.js:4867
                                set value @ app.js:5439
                                swapComponent @ app.js:10
                                (anonymous) @ app.js:22
                                Promise.then (async)
                                setPage @ app.js:22
                                (anonymous) @ app.js:22
                                Promise.then (async)
                                visit @ app.js:22
                                onClick @ app.js:10
                                callWithErrorHandling @ app.js:5834
                                callWithAsyncErrorHandling @ app.js:5843
                                invoker @ app.js:13211

                                Responder
                                • guti dice

                                  14/04/2021 en 00:23

                                  Hola Rodrigo ¿has clonado el repositorio de Github ( https://github.com/gutifer666/Inertia-Dickinson.git )? Yo lo he hecho y la función de editar me funciona correctamente ¿Estás utilizando estilos dentro de los componentes?

                                  Responder
                                  • carlos dice

                                    24/04/2021 en 03:15

                                    También me encuentro con el mismo caso

                                    Responder
                                • Zeta Pampa dice

                                  27/04/2021 en 21:20

                                  Intenta loguearte con un usuario previamente.

                                  Responder
                              2. carlos dice

                                23/04/2021 en 22:50

                                Excelente guía

                                Responder
                              3. enrique dice

                                16/06/2021 en 13:11

                                me ocurre lo mismo el error al editar

                                Responder

                              Responder a guti Cancelar la respuesta

                              Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

                              This site uses Akismet to reduce spam. Learn how your comment data is processed.

                              Copyright © 2025 · Genesis Sample on Genesis Framework · WordPress · Iniciar sesión

                              Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Si continúas usando este sitio, asumiremos que estás de acuerdo con ello.AceptarPolítica de privacidad