crear controladores separados para API y Web

 crear controladores separados para API y Web. Te explico por qué y te muestro cómo hacerlo:

¿Por qué separar los controladores?

  1. Separación de responsabilidades: Cada controlador tiene un propósito específico

  2. Mantenimiento más fácil: Cambios en API no afectan la web y viceversa

  3. Diferentes lógicas: API necesita manejo de JSON, autenticación diferente, etc.

  4. Mejor organización: Código más limpio y escalable

Estructura recomendada

bash
# Controlador para Web
php artisan make:controller Web/ProductController --resource --model=Product

# Controlador para API  
php artisan make:controller Api/ProductController --resource --model=Product

1. Controlador para API

app/Http/Controllers/Api/ProductController.php:

php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): JsonResponse
    {
        $products = Product::all();
        
        return response()->json([
            'success' => true,
            'data' => $products,
            'message' => 'Products retrieved successfully'
        ]);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): JsonResponse
    {
        $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);

        return response()->json([
            'success' => true,
            'data' => $product,
            'message' => 'Product created successfully'
        ], 201);
    }

    /**
     * Display the specified resource.
     */
    public function show(Product $product): JsonResponse
    {
        return response()->json([
            'success' => true,
            'data' => $product,
            'message' => 'Product retrieved successfully'
        ]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Product $product): JsonResponse
    {
        $validated = $request->validate([
            'name' => 'sometimes|required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'sometimes|required|numeric|min:0',
            'stock' => 'sometimes|required|integer|min:0'
        ]);

        $product->update($validated);

        return response()->json([
            'success' => true,
            'data' => $product,
            'message' => 'Product updated successfully'
        ]);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Product $product): JsonResponse
    {
        $product->delete();

        return response()->json([
            'success' => true,
            'message' => 'Product deleted successfully'
        ], 204);
    }
}

2. Controlador para Web

app/Http/Controllers/Web/ProductController.php:

php
<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): View
    {
        $products = Product::latest()->paginate(10);
        return view('products.index', compact('products'));
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create(): View
    {
        return view('products.create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0'
        ]);

        Product::create($validated);

        return redirect()->route('products.index')
            ->with('success', 'Producto creado exitosamente.');
    }

    /**
     * Display the specified resource.
     */
    public function show(Product $product): View
    {
        return view('products.show', compact('product'));
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Product $product): View
    {
        return view('products.edit', compact('product'));
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Product $product): RedirectResponse
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0'
        ]);

        $product->update($validated);

        return redirect()->route('products.index')
            ->with('success', 'Producto actualizado exitosamente.');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Product $product): RedirectResponse
    {
        $product->delete();

        return redirect()->route('products.index')
            ->with('success', 'Producto eliminado exitosamente.');
    }
}

3. Rutas separadas

routes/web.php:

php
<?php

use App\Http\Controllers\Web\ProductController;
use Illuminate\Support\Facades\Route;

Route::resource('products', ProductController::class);

 Rutas Web (routes/web.php)

php
<?php
use App\Http\Controllers\Web\ProductController;
use Illuminate\Support\Facades\Route;
// Rutas normales en lugar de resource
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');
// Alternativa más concisa usando resource si prefieres
// Route::resource('products', ProductController::class);

routes/api.php:

php
<?php

use App\Http\Controllers\Api\ProductController;
use Illuminate\Support\Facades\Route;

Route::apiResource('products', ProductController::class);

1. Rutas API (routes/api.php)

php
<?php

use App\Http\Controllers\Api\ProductController;
use Illuminate\Support\Facades\Route;

// Rutas normales en lugar de resource
Route::get('products', [ProductController::class, 'index']);
Route::post('products', [ProductController::class, 'store']);
Route::get('products/{product}', [ProductController::class, 'show']);
Route::put('products/{product}', [ProductController::class, 'update']);
Route::patch('products/{product}', [ProductController::class, 'update']);
Route::delete('products/{product}', [ProductController::class, 'destroy']);

// Alternativa más concisa usando apiResource si prefieres
// Route::apiResource('products', ProductController::class);

Ventajas de usar rutas normales:

  1. Más control: Puedes personalizar cada ruta individualmente

  2. Middleware específico: Puedes aplicar middleware diferentes a rutas específicas

  3. Nombres personalizados: Puedes cambiar los nombres de las rutas

  4. Excluir métodos: Es más fácil excluir métodos que no necesitas

Ejemplo con middleware específico:

php
// Solo usuarios autenticados pueden crear, editar y eliminar
Route::get('products', [ProductController::class, 'index'])->name('products.index');
Route::get('products/{product}', [ProductController::class, 'show'])->name('products.show');

// Rutas protegidas
Route::middleware(['auth'])->group(function () {
    Route::get('products/create', [ProductController::class, 'create'])->name('products.create');
    Route::post('products', [ProductController::class, 'store'])->name('products.store');
    Route::get('products/{product}/edit', [ProductController::class, 'edit'])->name('products.edit');
    Route::put('products/{product}', [ProductController::class, 'update'])->name('products.update');
    Route::delete('products/{product}', [ProductController::class, 'destroy'])->name('products.destroy');
});

4. Form Request para validación (Opcional pero recomendado)

Para mejor organización, puedes crear Form Requests:

bash
php artisan make:request Api/StoreProductRequest
php artisan make:request Api/UpdateProductRequest

app/Http/Requests/Api/StoreProductRequest.php:

php
<?php

namespace App\Http\Requests\Api;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0'
        ];
    }
}

Y luego usarlo en el controlador API:

php
public function store(StoreProductRequest $request): JsonResponse
{
    $validated = $request->validated();
    $product = Product::create($validated);
    
    return response()->json([
        'success' => true,
        'data' => $product,
        'message' => 'Product created successfully'
    ], 201);
}

5. Ventajas de esta estructura

  1. Separación clara: API y Web completamente independientes

  2. Mantenimiento fácil: Cambios en uno no afectan al otro

  3. Escalabilidad: Puedes agregar características específicas para cada uno

  4. Testing más fácil: Tests unitarios separados

  5. Equipos paralelos: Diferentes equipos pueden trabajar simultáneamente

6. Ejemplo de uso con API

bash
# Crear producto
curl -X POST http://localhost:8000/api/products \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "name": "Laptop Gaming",
    "price": 1299.99,
    "stock": 15,
    "description": "High performance gaming laptop"
  }'

# Respuesta esperada:
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Laptop Gaming",
    "description": "High performance gaming laptop",
    "price": "1299.99",
    "stock": 15,
    "created_at": "2023-12-20T10:00:00.000000Z",
    "updated_at": "2023-12-20T10:00:00.000000Z"
  },
  "message": "Product created successfully"
}

Esta estructura es la mejor práctica para proyectos Laravel que necesitan tanto API como interfaz web

Comentarios

Entradas más populares de este blog

Laravel tanto para web como para API al mismo tiempo.

Creación de una API RESTful con Laravel (sin autenticación)