Laravel tanto para web como para API al mismo tiempo.
Te explico cómo crear un CRUD completo en Laravel tanto para web como para API al mismo tiempo.
1. Configuración Inicial
Primero, crea el modelo con migración y controlador:
php artisan make:model Product -mcr2. Migración
database/migrations/xxxx_create_products_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->integer('stock');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
};3. Modelo
app/Models/Product.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'price',
'stock'
];
protected $casts = [
'price' => 'decimal:2',
];
}4. Controlador Unificado (Web + API)
app/Http/Controllers/ProductController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
class ProductController extends Controller
{
// WEB: Mostrar lista de productos
public function index(): View
{
$products = Product::latest()->paginate(10);
return view('products.index', compact('products'));
}
// WEB: Mostrar formulario de creación
public function create(): View
{
return view('products.create');
}
// WEB/API: Almacenar nuevo producto
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0'
]);
$product = Product::create($validated);
if ($request->wantsJson()) {
return response()->json($product, 201);
}
return redirect()->route('products.index')
->with('success', 'Producto creado exitosamente.');
}
// WEB: Mostrar producto específico
public function show(Product $product): View
{
return view('products.show', compact('product'));
}
// WEB: Mostrar formulario de edición
public function edit(Product $product): View
{
return view('products.edit', compact('product'));
}
// WEB/API: Actualizar producto
public function update(Request $request, Product $product)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0'
]);
$product->update($validated);
if ($request->wantsJson()) {
return response()->json($product);
}
return redirect()->route('products.index')
->with('success', 'Producto actualizado exitosamente.');
}
// WEB/API: Eliminar producto
public function destroy(Request $request, Product $product)
{
$product->delete();
if ($request->wantsJson()) {
return response()->json(null, 204);
}
return redirect()->route('products.index')
->with('success', 'Producto eliminado exitosamente.');
}
// API: Métodos específicos para API
public function apiIndex(): JsonResponse
{
$products = Product::all();
return response()->json($products);
}
public function apiShow(Product $product): JsonResponse
{
return response()->json($product);
}
}5. Rutas
routes/web.php:
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::resource('products', ProductController::class);
*****************************************Route::get('products', [ProductController::class, 'index'])->name('products.index');
Route::get('products/create', [ProductController::class, 'create'])->name('products.create');
Route::post('products', [ProductController::class, 'store'])->name('products.store');
Route::get('products/{product}', [ProductController::class, 'show'])->name('products.show');
Route::get('products/{product}/edit', [ProductController::class, 'edit'])->name('products.edit');
Route::put('products/{product}', [ProductController::class, 'update'])->name('products.update');
Route::patch('products/{product}', [ProductController::class, 'update']);
Route::delete('products/{product}', [ProductController::class, 'destroy'])->name('products.destroy');routes/api.php:
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::apiResource('products', ProductController::class);
Route::get('products', [ProductController::class, 'apiIndex']);
Route::get('products/{product}', [ProductController::class, 'apiShow']);
*********************<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
// Rutas normales para API
Route::get('products', [ProductController::class, 'apiIndex']); // Usando el método específico para API
Route::post('products', [ProductController::class, 'store']);
Route::get('products/{product}', [ProductController::class, 'apiShow']); // Usando el método específico para API
Route::put('products/{product}', [ProductController::class, 'update']);
Route::patch('products/{product}', [ProductController::class, 'update']);
Route::delete('products/{product}', [ProductController::class, 'destroy']);
// O si prefieres usar los métodos principales con validación de JSON:
Route::get('products', [ProductController::class, 'index']); // Este mostraría HTML para web, no recomendado para API pura
Route::get('products/{product}', [ProductController::class, 'show']); // Mismo caso que arriba
// Alternativa usando apiResource (si prefieres mantenerlo)
// Route::apiResource('products', ProductController::class);
Observaciones importantes:
Problema de diseño: Tu controlador actual tiene un conflicto porque los métodos index() y show() devuelven vistas (View) para web, pero la API necesita JSON.
Recomendación: Sería mejor separar los controladores como en tu primer ejemplo, o modificar los métodos para detectar el tipo de request:
// En el método index del controlador
public function index(Request $request)
{
$products = Product::latest()->paginate(10);
if ($request->wantsJson()) {
return response()->json($products);
}
return view('products.index', compact('products'));
}Versión optimizada de rutas API:
// routes/api.php
Route::get('products', [ProductController::class, 'apiIndex']);
Route::post('products', [ProductController::class, 'store']);
Route::get('products/{product}', [ProductController::class, 'apiShow']);
Route::put('products/{product}', [ProductController::class, 'update']);
Route::patch('products/{product}', [ProductController::class, 'update']);
Route::delete('products/{product}', [ProductController::class, 'destroy']);
Si quieres un enfoque más limpio:
Te recomiendo mantener la separación de controladores que tenías inicialmente (Web/ProductController y Api/ProductController) ya que evita la lógica condicional y hace el código más mantenible.
6. Vistas (Web)
resources/views/products/index.blade.php:
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Productos</h1>
<a href="{{ route('products.create') }}" class="btn btn-primary mb-3">Nuevo Producto</a>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Precio</th>
<th>Stock</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
@foreach($products as $product)
<tr>
<td>{{ $product->id }}</td>
<td>{{ $product->name }}</td>
<td>${{ number_format($product->price, 2) }}</td>
<td>{{ $product->stock }}</td>
<td>
<a href="{{ route('products.show', $product) }}" class="btn btn-info btn-sm">Ver</a>
<a href="{{ route('products.edit', $product) }}" class="btn btn-warning btn-sm">Editar</a>
<form action="{{ route('products.destroy', $product) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('¿Eliminar?')">Eliminar</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $products->links() }}
</div>
@endsectionresources/views/products/create.blade.php:
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Crear Producto</h1>
<form action="{{ route('products.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="name" class="form-label">Nombre</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="description" class="form-label">Descripción</label>
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
</div>
<div class="mb-3">
<label for="price" class="form-label">Precio</label>
<input type="number" step="0.01" class="form-control" id="price" name="price" required>
</div>
<div class="mb-3">
<label for="stock" class="form-label">Stock</label>
<input type="number" class="form-control" id="stock" name="stock" required>
</div>
<button type="submit" class="btn btn-primary">Crear</button>
<a href="{{ route('products.index') }}" class="btn btn-secondary">Cancelar</a>
</form>
</div>
@endsection7. Ejecutar Migración
php artisan migrate8. Probar las Rutas
Web:
GET /products- Lista de productosGET /products/create- Formulario de creaciónPOST /products- Crear productoGET /products/{id}- Ver productoGET /products/{id}/edit- Editar productoPUT/PATCH /products/{id}- Actualizar productoDELETE /products/{id}- Eliminar producto
API:
GET /api/products- Lista de productos (JSON)POST /api/products- Crear producto (JSON)GET /api/products/{id}- Ver producto (JSON)PUT/PATCH /api/products/{id}- Actualizar producto (JSON)DELETE /api/products/{id}- Eliminar producto
9. Ejemplo de Uso con API
# Crear producto
curl -X POST http://localhost:8000/api/products \
-H "Content-Type: application/json" \
-d '{"name": "Laptop", "price": 999.99, "stock": 10, "description": "Gaming laptop"}'
# Listar productos
curl http://localhost:8000/api/products
# Actualizar producto
curl -X PUT http://localhost:8000/api/products/1 \
-H "Content-Type: application/json" \
-d '{"name": "Laptop Pro", "price": 1299.99, "stock": 5}'
# Eliminar producto
curl -X DELETE http://localhost:8000/api/products/1Este enfoque te permite tener un CRUD completo tanto para interfaz web como para API RESTful usando el mismo controlador, optimizando el código y manteniendo la consistencia entre ambas interfaces
Comentarios
Publicar un comentario