Crear el proyecto
Arrancamos con un proyecto Laravel limpio. Si ya tienes Composer instalado, corre esto en tu terminal:
composer create-project laravel/laravel micrud
cd micrud
php artisan serve
El servidor queda en http://localhost:8000. Dejalo corriendo mientras trabajamos.
Configurar la base de datos
Abre el archivo .env en la raíz del proyecto y ajusta los datos de tu base de datos local:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=micrud
DB_USERNAME=root
DB_PASSWORD=
micrud en tu gestor (phpMyAdmin, TablePlus, DBeaver) antes de continuar. Laravel no la crea automáticamente.
Crear la migración
Generamos la migración para la tabla de productos:
php artisan make:migration create_products_table
Laravel crea el archivo en database/migrations/. Ábrelo y define las columnas:
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->timestamps();
});
}
Ejecuta la migración para crear la tabla en la base de datos:
php artisan migrate
El Modelo
Crea el modelo con Artisan:
php artisan make:model Product
Abre app/Models/Product.php y declara los campos asignables en masa con $fillable:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'name',
'description',
'price',
];
}
$fillable, Laravel bloquea la asignación masiva y el método Product::create() no funcionará. Siempre declara los campos que el usuario puede llenar.
El Controlador resource
Generamos el controlador con los 7 métodos RESTful ya estructurados:
php artisan make:controller ProductController --resource
Abre app/Http/Controllers/ProductController.php y completa cada método:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// Listar todos los productos
public function index()
{
$products = Product::all();
return view('products.index', compact('products'));
}
// Mostrar formulario de creación
public function create()
{
return view('products.create');
}
// Guardar nuevo producto
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'price' => 'required|numeric',
]);
Product::create($request->all());
return redirect()->route('products.index')
->with('success', 'Producto creado correctamente.');
}
// Mostrar un producto
public function show(Product $product)
{
return view('products.show', compact('product'));
}
// Formulario de edición
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}
// Actualizar producto
public function update(Request $request, Product $product)
{
$request->validate([
'name' => 'required',
'price' => 'required|numeric',
]);
$product->update($request->all());
return redirect()->route('products.index')
->with('success', 'Producto actualizado correctamente.');
}
// Eliminar producto
public function destroy(Product $product)
{
$product->delete();
return redirect()->route('products.index')
->with('success', 'Producto eliminado.');
}
}
Las rutas
Una sola línea en routes/web.php genera las 7 rutas RESTful automáticamente:
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::resource('products', ProductController::class);
Verifica que las rutas quedaron bien con:
php artisan route:list --path=products
Deberías ver esto:
GET /products products.index
GET /products/create products.create
POST /products products.store
GET /products/{product} products.show
GET /products/{product}/edit products.edit
PUT /products/{product} products.update
DELETE /products/{product} products.destroy
Las vistas Blade
Crea la carpeta resources/views/products/ y los siguientes archivos:
index.blade.php — listado con botón eliminar y mensajes flash:
@if(session('success'))
<div class="alert">{{ session('success') }}</div>
@endif
<a href="{{ route('products.create') }}">Nuevo producto</a>
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
@foreach($products as $product)
<tr>
<td>{{ $product->name }}</td>
<td>{{ $product->price }}</td>
<td>
<a href="{{ route('products.edit', $product) }}">Editar</a>
<form action="{{ route('products.destroy', $product) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit">Eliminar</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
create.blade.php — formulario de creación con errores de validación:
<form action="{{ route('products.store') }}" method="POST">
@csrf
<label>Nombre</label>
<input type="text" name="name" value="{{ old('name') }}">
@error('name') <span>{{ $message }}</span> @enderror
<label>Descripción</label>
<textarea name="description">{{ old('description') }}</textarea>
<label>Precio</label>
<input type="number" step="0.01" name="price" value="{{ old('price') }}">
@error('price') <span>{{ $message }}</span> @enderror
<button type="submit">Guardar</button>
</form>
edit.blade.php — igual que create pero con los valores actuales y método PUT:
<form action="{{ route('products.update', $product) }}" method="POST">
@csrf
@method('PUT')
<label>Nombre</label>
<input type="text" name="name" value="{{ old('name', $product->name) }}">
@error('name') <span>{{ $message }}</span> @enderror
<label>Descripción</label>
<textarea name="description">{{ old('description', $product->description) }}</textarea>
<label>Precio</label>
<input type="number" step="0.01" name="price" value="{{ old('price', $product->price) }}">
@error('price') <span>{{ $message }}</span> @enderror
<button type="submit">Actualizar</button>
</form>
old('name', $product->name) es el patrón correcto en el formulario de edición: usa el valor del request previo si hubo error de validación, y si no usa el valor actual del modelo.
Probar el flujo completo
Con el servidor corriendo en php artisan serve, entra a:
http://localhost:8000/products
El flujo completo que deberías poder hacer sin errores:
- Crear un producto nuevo desde
/products/create - Ver el listado con el producto recién creado
- Editar el producto y guardar los cambios
- Eliminar el producto y confirmar que desaparece del listado
- Intentar crear un producto sin nombre — el formulario debe mostrar el error de validación