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
- Paso 6: Crear rutas y protegerlas con Laravel Sanctum
- Paso 7: Modificar Layout y crear Páginas con Laravel Inertia
- 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:
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:
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:
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:
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:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
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:
xxxxxxxxxx
php artisan make:controller ProductController --resource --model=Product
El controlador quedaría ubicado en app/http/controllers:
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:
xxxxxxxxxx
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:
xxxxxxxxxx
public function index()
{
$products = Product::all();
return Inertia::render('List', ['products' => $products]);
}
Añadir Producto
Muestra el formulario para crear un nuevo producto:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
xxxxxxxxxx
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:
xxxxxxxxxx
php artisan route:list
Obtendremos información sobre las rutas creadas:
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>:
xxxxxxxxxx
<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<jet-nav-link
:href="route('products.index')"
:active="route().current('products.index')"
>
Productos
</jet-nav-link>
<jet-nav-link
:href="route('products.create')"
:active="route().current('products.create')"
>
Añadir
</jet-nav-link>
</div>
</div>
De manera similar creamos el menú Responsive. esta vez haciendo uso del componente ResponsiveNavLink.vue:
xxxxxxxxxx
<!-- Responsive Navigation Menu -->
<div
:class="{
block: showingNavigationDropdown,
hidden: !showingNavigationDropdown,
}"
class="sm:hidden"
>
<div class="pt-2 pb-3 space-y-1">
<jet-responsive-nav-link
:href="route('products.index')"
:active="route().current('products.index')"
>
Productos
</jet-responsive-nav-link>
<jet-responsive-nav-link
:href="route('products.create')"
:active="route().current('products.create')"
>
añadir
</jet-responsive-nav-link>
</div>
El script de nuestro Layout quedaría como sigue:
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:
xxxxxxxxxx
<template>
<app-layout>
<table>
<thead>
<tr>
<th>
Producto
</th>
<th >
Precio
</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>
{{ product.name }}
</td>
<td>
<div>{{ product.price }}</div>
<div> Euros </div>
</td>
<td>
<inertia-link :href="route('products.edit', product.id)">
Editar
</inertia-link>
</td>
<td>
<inertia-link
method="delete"
:href="route('products.destroy', product.id)">
Borrar
</inertia-link>
</td>
</tr>
</tbody>
</table>
</app-layout>
</template>
En el script declaramos la propiedad products e importamos el componente AppLayout.vue:
xxxxxxxxxx
<script>
import AppLayout from "@/Layouts/AppLayout";
export default {
props: {
products: Array,
},
components: {
AppLayout,
},
};
</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
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:
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
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:
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
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 ).
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
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?
También me encuentro con el mismo caso
Intenta loguearte con un usuario previamente.
Excelente guía
me ocurre lo mismo el error al editar