Sistema CRUD con API REST y Web en Laravel 12

 

Sistema CRUD con API REST y Web en Laravel 12

Basado en el tutorial que me pasaste, aquí tienes una versión extendida que incluye tanto la interfaz web como una API REST en el mismo proyecto.


📚 Índice

  1. Configuración inicial

  2. Migración, Seeder y Modelo

  3. Creación de Controladores

  4. Definición de Rutas

  5. Creación de Vistas Web

  6. Pruebas de la API

  7. Comandos Útiles


1️⃣ Configuración inicial

bash
# Verifica tu versión de PHP (mínimo 8.2 para Laravel 12)
php -v

# Verifica tu versión de Composer
composer -V

# Crea un nuevo proyecto Laravel
composer create-project laravel/laravel pasteles-app
cd pasteles-app

# Configura tu archivo .env con los datos de tu base de datos
# .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=pasteles_db
DB_USERNAME=root
DB_PASSWORD=

2️⃣ Migración, Seeder y Modelo

2.1 Crear la migración

bash
php artisan make:migration create_pasteles_table

database/migrations/[timestamp]_create_pasteles_table.php

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('pasteles', function (Blueprint $table) {
            $table->id();
            $table->string('nombre', 100);
            $table->enum('sabor', ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']);
            $table->decimal('precio', 8, 2)->nullable();
            $table->boolean('disponible')->default(true);
            $table->text('descripcion')->nullable();
            $table->timestamps();
            $table->softDeletes(); // Eliminación lógica
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('pasteles');
    }
};

2.2 Crear el Modelo

bash
php artisan make:model Pastel

app/Models/Pastel.php

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Pastel extends Model
{
    use HasFactory, SoftDeletes;

    protected $table = 'pasteles';

    protected $fillable = [
        'nombre',
        'sabor',
        'precio',
        'disponible',
        'descripcion'
    ];

    protected $casts = [
        'precio' => 'decimal:2',
        'disponible' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    // Scopes para consultas reutilizables
    public function scopeDisponibles($query)
    {
        return $query->where('disponible', true);
    }

    public function scopeBySabor($query, $sabor)
    {
        return $query->where('sabor', $sabor);
    }
}

2.3 Crear el Factory (para datos de prueba)

bash
php artisan make:factory PastelFactory

database/factories/PastelFactory.php

php
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PastelFactory extends Factory
{
    public function definition(): array
    {
        $sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
        
        return [
            'nombre' => 'Pastel ' . $this->faker->unique()->firstName() . ' ' . $this->faker->lastName(),
            'sabor' => $this->faker->randomElement($sabores),
            'precio' => $this->faker->randomFloat(2, 50, 500),
            'disponible' => $this->faker->boolean(80),
            'descripcion' => $this->faker->sentence(10),
            'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
            'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
        ];
    }
}

2.4 Crear el Seeder

bash
php artisan make:seeder PastelSeeder

database/seeders/PastelSeeder.php

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\Pastel;

class PastelSeeder extends Seeder
{
    public function run(): void
    {
        Pastel::factory(50)->create();
    }
}

database/seeders/DatabaseSeeder.php

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            PastelSeeder::class,
        ]);
    }
}

2.5 Ejecutar migraciones y seeders

bash
php artisan migrate --seed

3️⃣ Creación de Controladores

🎯 ¡IMPORTANTE! Vamos a crear DOS controladores:

  1. PastelController - Para la WEB (vistas HTML)

  2. PastelApiController - Para la API REST (devuelve JSON)

bash
# Controlador para la WEB
php artisan make:controller PastelController --resource

# Controlador para la API
php artisan make:controller PastelApiController

3.1 Controlador WEB (PastelController)

app/Http/Controllers/PastelController.php

php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use Illuminate\Http\Request;

class PastelController extends Controller
{
    /**
     * Muestra la lista de pasteles (Vista web)
     */
    public function index()
    {
        $pasteles = Pastel::all();
        return view('pasteles.index', compact('pasteles'));
    }

    /**
     * Muestra el formulario para crear un nuevo pastel
     */
    public function create()
    {
        return view('pasteles.create');
    }

    /**
     * Almacena un nuevo pastel (Desde el formulario web)
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'nombre' => 'required|string|max:100',
            'sabor' => 'required|string|max:50',
            'precio' => 'required|numeric|min:0',
            'disponible' => 'nullable|boolean',
            'descripcion' => 'nullable|string|max:1000',
        ]);

        Pastel::create($validated);

        return redirect()
            ->route('pasteles.index')
            ->with('success', '¡Pastel creado exitosamente!');
    }

    /**
     * Muestra un pastel específico (Vista web)
     */
    public function show(Pastel $pastel)
    {
        return view('pasteles.show', compact('pastel'));
    }

    /**
     * Muestra el formulario para editar un pastel
     */
    public function edit(Pastel $pastel)
    {
        return view('pasteles.edit', compact('pastel'));
    }

    /**
     * Actualiza un pastel (Desde el formulario web)
     */
    public function update(Request $request, Pastel $pastel)
    {
        $validated = $request->validate([
            'nombre' => 'required|string|max:100',
            'sabor' => 'required|string|max:50',
            'precio' => 'required|numeric|min:0',
            'disponible' => 'nullable|boolean',
            'descripcion' => 'nullable|string|max:1000',
        ]);

        $pastel->update($validated);

        return redirect()
            ->route('pasteles.index')
            ->with('success', '¡Pastel actualizado exitosamente!');
    }

    /**
     * Elimina un pastel (Eliminación lógica)
     */
    public function destroy(Pastel $pastel)
    {
        $pastel->delete();

        return redirect()
            ->route('pasteles.index')
            ->with('success', '¡Pastel eliminado exitosamente!');
    }
}

3.2 Controlador API (PastelApiController)

app/Http/Controllers/PastelApiController.php

php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class PastelApiController extends Controller
{
    /**
     * GET /api/pasteles
     * Lista todos los pasteles (Formato JSON)
     */
    public function index()
    {
        $pasteles = Pastel::all();
        
        return response()->json([
            'success' => true,
            'data' => $pasteles,
            'message' => 'Lista de pasteles obtenida exitosamente'
        ], 200);
    }

    /**
     * POST /api/pasteles
     * Crea un nuevo pastel
     */
    public function store(Request $request)
    {
        // Validación
        $validated = $request->validate([
            'nombre' => 'required|string|max:100',
            'sabor' => ['required', Rule::in(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'])],
            'precio' => 'required|numeric|min:0',
            'disponible' => 'sometimes|boolean',
            'descripcion' => 'nullable|string|max:1000',
        ]);

        // Si no viene 'disponible', lo ponemos en true por defecto
        if (!isset($validated['disponible'])) {
            $validated['disponible'] = true;
        }

        $pastel = Pastel::create($validated);

        return response()->json([
            'success' => true,
            'data' => $pastel,
            'message' => 'Pastel creado exitosamente'
        ], 201);
    }

    /**
     * GET /api/pasteles/{id}
     * Muestra un pastel específico
     */
    public function show($id)
    {
        $pastel = Pastel::find($id);

        if (!$pastel) {
            return response()->json([
                'success' => false,
                'message' => 'Pastel no encontrado'
            ], 404);
        }

        return response()->json([
            'success' => true,
            'data' => $pastel,
            'message' => 'Pastel encontrado'
        ], 200);
    }

    /**
     * PUT /api/pasteles/{id}
     * Actualiza un pastel completo
     */
    public function update(Request $request, $id)
    {
        $pastel = Pastel::find($id);

        if (!$pastel) {
            return response()->json([
                'success' => false,
                'message' => 'Pastel no encontrado'
            ], 404);
        }

        $validated = $request->validate([
            'nombre' => 'sometimes|string|max:100',
            'sabor' => ['sometimes', Rule::in(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'])],
            'precio' => 'sometimes|numeric|min:0',
            'disponible' => 'sometimes|boolean',
            'descripcion' => 'nullable|string|max:1000',
        ]);

        $pastel->update($validated);

        return response()->json([
            'success' => true,
            'data' => $pastel->fresh(),
            'message' => 'Pastel actualizado exitosamente'
        ], 200);
    }

    /**
     * DELETE /api/pasteles/{id}
     * Elimina un pastel (Eliminación lógica)
     */
    public function destroy($id)
    {
        $pastel = Pastel::find($id);

        if (!$pastel) {
            return response()->json([
                'success' => false,
                'message' => 'Pastel no encontrado'
            ], 404);
        }

        $pastel->delete();

        return response()->json([
            'success' => true,
            'message' => 'Pastel eliminado exitosamente'
        ], 200);
    }

    /**
     * GET /api/pasteles/disponibles
     * Obtiene solo los pasteles disponibles
     */
    public function disponibles()
    {
        $pasteles = Pastel::disponibles()->get();

        return response()->json([
            'success' => true,
            'data' => $pasteles,
            'message' => 'Pasteles disponibles obtenidos'
        ], 200);
    }

    /**
     * GET /api/pasteles/sabor/{sabor}
     * Obtiene pasteles por sabor
     */
    public function bySabor($sabor)
    {
        $pasteles = Pastel::bySabor($sabor)->get();

        if ($pasteles->isEmpty()) {
            return response()->json([
                'success' => false,
                'message' => 'No se encontraron pasteles del sabor ' . $sabor
            ], 404);
        }

        return response()->json([
            'success' => true,
            'data' => $pasteles,
            'message' => 'Pasteles del sabor ' . $sabor . ' obtenidos'
        ], 200);
    }
}

4️⃣ Definición de Rutas

routes/web.php - Rutas para la WEB

php
<?php

use App\Http\Controllers\PastelController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return redirect()->route('pasteles.index');
});

// Rutas para la WEB - CRUD completo
Route::resource('pasteles', PastelController::class);

routes/api.php - Rutas para la API REST

php
<?php

use App\Http\Controllers\PastelApiController;
use Illuminate\Support\Facades\Route;

// Todas las rutas de la API tienen el prefijo /api
// y devuelven respuestas en formato JSON

Route::get('/pasteles', [PastelApiController::class, 'index']);           // GET    /api/pasteles
Route::post('/pasteles', [PastelApiController::class, 'store']);          // POST   /api/pasteles
Route::get('/pasteles/{id}', [PastelApiController::class, 'show']);       // GET    /api/pasteles/1
Route::put('/pasteles/{id}', [PastelApiController::class, 'update']);     // PUT    /api/pasteles/1
Route::delete('/pasteles/{id}', [PastelApiController::class, 'destroy']); // DELETE /api/pasteles/1

// Rutas adicionales de la API
Route::get('/pasteles/disponibles', [PastelApiController::class, 'disponibles']);   // GET /api/pasteles/disponibles
Route::get('/pasteles/sabor/{sabor}', [PastelApiController::class, 'bySabor']);    // GET /api/pasteles/sabor/chocolate

📌 Ver todas las rutas

bash
php artisan route:list

5️⃣ Creación de Vistas Web

5.1 Layout principal

resources/views/layouts/app.blade.php

html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Pastelería')</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{{ route('pasteles.index') }}">🍰 Pastelería</a>
            <a href="{{ route('pasteles.create') }}" class="btn btn-primary">+ Agregar Pastel</a>
        </div>
    </nav>

    <div class="container mt-4">
        @if(session('success'))
            <div class="alert alert-success alert-dismissible fade show" role="alert">
                {{ session('success') }}
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
        @endif

        @yield('content')
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

5.2 Vista Index (Lista de pasteles)

resources/views/pasteles/index.blade.php

html
@extends('layouts.app')

@section('title', 'Lista de Pasteles')

@section('content')
    <h1 class="mb-4">📋 Lista de Pasteles</h1>

    <div class="row">
        @foreach($pasteles as $pastel)
            <div class="col-md-4 mb-4">
                <div class="card h-100">
                    <div class="card-body">
                        <h5 class="card-title">{{ $pastel->nombre }}</h5>
                        <p class="card-text">
                            <strong>Sabor:</strong> {{ $pastel->sabor }}<br>
                            <strong>Precio:</strong> ${{ number_format($pastel->precio, 2) }}<br>
                            <strong>Disponible:</strong> 
                            <span class="badge {{ $pastel->disponible ? 'bg-success' : 'bg-danger' }}">
                                {{ $pastel->disponible ? 'Sí' : 'No' }}
                            </span>
                        </p>
                        <p class="card-text small">{{ Str::limit($pastel->descripcion, 100) }}</p>
                    </div>
                    <div class="card-footer bg-transparent">
                        <div class="btn-group w-100" role="group">
                            <a href="{{ route('pasteles.show', $pastel) }}" class="btn btn-info btn-sm">Ver</a>
                            <a href="{{ route('pasteles.edit', $pastel) }}" class="btn btn-warning btn-sm">Editar</a>
                            <form action="{{ route('pasteles.destroy', $pastel) }}" method="POST" class="d-inline">
                                @csrf
                                @method('DELETE')
                                <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('¿Eliminar este pastel?')">
                                    Eliminar
                                </button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        @endforeach
    </div>

    @if($pasteles->isEmpty())
        <div class="alert alert-info">
            No hay pasteles registrados. <a href="{{ route('pasteles.create') }}">¡Crea el primero!</a>
        </div>
    @endif
@endsection

5.3 Vista Create (Crear pastel)

resources/views/pasteles/create.blade.php

html
@extends('layouts.app')

@section('title', 'Crear Pastel')

@section('content')
    <h1 class="mb-4">🍰 Crear Nuevo Pastel</h1>

    <div class="card">
        <div class="card-body">
            <form action="{{ route('pasteles.store') }}" method="POST">
                @csrf

                <div class="mb-3">
                    <label for="nombre" class="form-label">Nombre</label>
                    <input type="text" class="form-control @error('nombre') is-invalid @enderror" 
                           id="nombre" name="nombre" value="{{ old('nombre') }}" required>
                    @error('nombre')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <label for="sabor" class="form-label">Sabor</label>
                    <select class="form-select @error('sabor') is-invalid @enderror" 
                            id="sabor" name="sabor" required>
                        <option value="">Selecciona un sabor</option>
                        <option value="chocolate" {{ old('sabor') == 'chocolate' ? 'selected' : '' }}>Chocolate</option>
                        <option value="vainilla" {{ old('sabor') == 'vainilla' ? 'selected' : '' }}>Vainilla</option>
                        <option value="cheesecake" {{ old('sabor') == 'cheesecake' ? 'selected' : '' }}>Cheesecake</option>
                        <option value="fresa" {{ old('sabor') == 'fresa' ? 'selected' : '' }}>Fresa</option>
                        <option value="coco" {{ old('sabor') == 'coco' ? 'selected' : '' }}>Coco</option>
                    </select>
                    @error('sabor')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <label for="precio" class="form-label">Precio</label>
                    <input type="number" step="0.01" class="form-control @error('precio') is-invalid @enderror" 
                           id="precio" name="precio" value="{{ old('precio') }}" required>
                    @error('precio')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="disponible" 
                               name="disponible" value="1" {{ old('disponible') ? 'checked' : '' }}>
                        <label class="form-check-label" for="disponible">Disponible</label>
                    </div>
                </div>

                <div class="mb-3">
                    <label for="descripcion" class="form-label">Descripción</label>
                    <textarea class="form-control @error('descripcion') is-invalid @enderror" 
                              id="descripcion" name="descripcion" rows="3">{{ old('descripcion') }}</textarea>
                    @error('descripcion')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <button type="submit" class="btn btn-primary">Guardar Pastel</button>
                <a href="{{ route('pasteles.index') }}" class="btn btn-secondary">Cancelar</a>
            </form>
        </div>
    </div>
@endsection

5.4 Vista Show (Ver pastel)

resources/views/pasteles/show.blade.php

html
@extends('layouts.app')

@section('title', $pastel->nombre)

@section('content')
    <div class="d-flex justify-content-between align-items-center mb-4">
        <h1>{{ $pastel->nombre }}</h1>
        <div>
            <a href="{{ route('pasteles.edit', $pastel) }}" class="btn btn-warning">Editar</a>
            <a href="{{ route('pasteles.index') }}" class="btn btn-secondary">Volver</a>
        </div>
    </div>

    <div class="card">
        <div class="card-body">
            <dl class="row">
                <dt class="col-sm-3">ID</dt>
                <dd class="col-sm-9">{{ $pastel->id }}</dd>

                <dt class="col-sm-3">Nombre</dt>
                <dd class="col-sm-9">{{ $pastel->nombre }}</dd>

                <dt class="col-sm-3">Sabor</dt>
                <dd class="col-sm-9">{{ $pastel->sabor }}</dd>

                <dt class="col-sm-3">Precio</dt>
                <dd class="col-sm-9">${{ number_format($pastel->precio, 2) }}</dd>

                <dt class="col-sm-3">Disponible</dt>
                <dd class="col-sm-9">
                    <span class="badge {{ $pastel->disponible ? 'bg-success' : 'bg-danger' }}">
                        {{ $pastel->disponible ? 'Sí' : 'No' }}
                    </span>
                </dd>

                <dt class="col-sm-3">Descripción</dt>
                <dd class="col-sm-9">{{ $pastel->descripcion ?: 'Sin descripción' }}</dd>

                <dt class="col-sm-3">Creado</dt>
                <dd class="col-sm-9">{{ $pastel->created_at->format('d/m/Y H:i') }}</dd>

                <dt class="col-sm-3">Actualizado</dt>
                <dd class="col-sm-9">{{ $pastel->updated_at->format('d/m/Y H:i') }}</dd>
            </dl>
        </div>
    </div>
@endsection

5.5 Vista Edit (Editar pastel)

resources/views/pasteles/edit.blade.php

html
@extends('layouts.app')

@section('title', 'Editar ' . $pastel->nombre)

@section('content')
    <h1 class="mb-4">✏️ Editar Pastel: {{ $pastel->nombre }}</h1>

    <div class="card">
        <div class="card-body">
            <form action="{{ route('pasteles.update', $pastel) }}" method="POST">
                @csrf
                @method('PUT')

                <div class="mb-3">
                    <label for="nombre" class="form-label">Nombre</label>
                    <input type="text" class="form-control @error('nombre') is-invalid @enderror" 
                           id="nombre" name="nombre" value="{{ old('nombre', $pastel->nombre) }}" required>
                    @error('nombre')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <label for="sabor" class="form-label">Sabor</label>
                    <select class="form-select @error('sabor') is-invalid @enderror" 
                            id="sabor" name="sabor" required>
                        <option value="">Selecciona un sabor</option>
                        <option value="chocolate" {{ old('sabor', $pastel->sabor) == 'chocolate' ? 'selected' : '' }}>Chocolate</option>
                        <option value="vainilla" {{ old('sabor', $pastel->sabor) == 'vainilla' ? 'selected' : '' }}>Vainilla</option>
                        <option value="cheesecake" {{ old('sabor', $pastel->sabor) == 'cheesecake' ? 'selected' : '' }}>Cheesecake</option>
                        <option value="fresa" {{ old('sabor', $pastel->sabor) == 'fresa' ? 'selected' : '' }}>Fresa</option>
                        <option value="coco" {{ old('sabor', $pastel->sabor) == 'coco' ? 'selected' : '' }}>Coco</option>
                    </select>
                    @error('sabor')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <label for="precio" class="form-label">Precio</label>
                    <input type="number" step="0.01" class="form-control @error('precio') is-invalid @enderror" 
                           id="precio" name="precio" value="{{ old('precio', $pastel->precio) }}" required>
                    @error('precio')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="disponible" 
                               name="disponible" value="1" {{ old('disponible', $pastel->disponible) ? 'checked' : '' }}>
                        <label class="form-check-label" for="disponible">Disponible</label>
                    </div>
                </div>

                <div class="mb-3">
                    <label for="descripcion" class="form-label">Descripción</label>
                    <textarea class="form-control @error('descripcion') is-invalid @enderror" 
                              id="descripcion" name="descripcion" rows="3">{{ old('descripcion', $pastel->descripcion) }}</textarea>
                    @error('descripcion')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <button type="submit" class="btn btn-primary">Actualizar Pastel</button>
                <a href="{{ route('pasteles.index') }}" class="btn btn-secondary">Cancelar</a>
            </form>
        </div>
    </div>
@endsection

6️⃣ Pruebas de la API

6.1 Usando Postman o Insomnia

MétodoURLDescripción
GEThttp://localhost:8000/api/pastelesObtener todos los pasteles
GEThttp://localhost:8000/api/pasteles/1Obtener un pastel específico
POSThttp://localhost:8000/api/pastelesCrear un pastel
PUThttp://localhost:8000/api/pasteles/1Actualizar un pastel
DELETEhttp://localhost:8000/api/pasteles/1Eliminar un pastel
GEThttp://localhost:8000/api/pasteles/disponiblesObtener pasteles disponibles
GEThttp://localhost:8000/api/pasteles/sabor/chocolateObtener pasteles por sabor

6.2 Ejemplo de POST (Crear pastel)

Body (JSON):

json
{
    "nombre": "Pastel de Chocolate",
    "sabor": "chocolate",
    "precio": 250.50,
    "disponible": true,
    "descripcion": "Delicioso pastel de chocolate con cobertura"
}

Respuesta:

json
{
    "success": true,
    "data": {
        "id": 51,
        "nombre": "Pastel de Chocolate",
        "sabor": "chocolate",
        "precio": "250.50",
        "disponible": true,
        "descripcion": "Delicioso pastel de chocolate con cobertura",
        "created_at": "2026-09-03T12:00:00.000000Z",
        "updated_at": "2026-09-03T12:00:00.000000Z"
    },
    "message": "Pastel creado exitosamente"
}

6.3 Probar la API desde la terminal con cURL

bash
# Obtener todos los pasteles
curl http://localhost:8000/api/pasteles

# Obtener un pastel específico
curl http://localhost:8000/api/pasteles/1

# Crear un pastel
curl -X POST http://localhost:8000/api/pasteles \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Pastel de Fresa","sabor":"fresa","precio":180.00,"disponible":true,"descripcion":"Delicioso pastel de fresa"}'

# Actualizar un pastel
curl -X PUT http://localhost:8000/api/pasteles/1 \
  -H "Content-Type: application/json" \
  -d '{"precio":300.00}'

# Eliminar un pastel
curl -X DELETE http://localhost:8000/api/pasteles/1

# Obtener pasteles disponibles
curl http://localhost:8000/api/pasteles/disponibles

# Obtener pasteles por sabor
curl http://localhost:8000/api/pasteles/sabor/chocolate

7️⃣ Comandos Útiles

7.1 Comandos de migración

Comando¿Qué hace?
php artisan migrateEjecuta todas las migraciones pendientes
php artisan migrate:freshElimina todas las tablas y vuelve a ejecutar migraciones
php artisan migrate:fresh --seedReinicia y ejecuta los seeders
php artisan migrate:rollbackRevierte el último lote de migraciones
php artisan migrate:statusMuestra el estado de todas las migraciones

7.2 Comandos de limpieza y optimización

bash
# Limpiar caché
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear

# Optimizar para producción
php artisan optimize

# Ver todas las rutas
php artisan route:list

7.3 Iniciar el servidor

bash
# Iniciar servidor de desarrollo
php artisan serve

# El servidor estará disponible en:
# http://localhost:8000/pasteles (WEB)
# http://localhost:8000/api/pasteles (API)

🎯 Resumen de URLs

URLTipoDescripción
/pastelesWEBLista de pasteles
/pasteles/createWEBFormulario para crear
/pasteles/{id}WEBVer detalle
/pasteles/{id}/editWEBFormulario para editar
/api/pastelesAPILista de pasteles (JSON)
/api/pastelesAPICrear pastel (POST)
/api/pasteles/{id}APIVer/Actualizar/Eliminar
/api/pasteles/disponiblesAPIPasteles disponibles
/api/pasteles/sabor/{sabor}APIPasteles por sabor

📌 Commits para Git

bash
# Crear modelo y migración
git add database/migrations/ app/Models/Pastel.php
git commit -m "feat: crear modelo y migración de pasteles"

# Crear factory y seeder
git add database/factories/ database/seeders/
git commit -m "feat: crear factory y seeder para pasteles"

# Crear controladores
git add app/Http/Controllers/PastelController.php
git add app/Http/Controllers/PastelApiController.php
git commit -m "feat: crear controladores web y api para pasteles"

# Crear rutas
git add routes/web.php routes/api.php
git commit -m "feat: definir rutas web y api para pasteles"

# Crear vistas
git add resources/views/pasteles/
git commit -m "feat: crear vistas para CRUD de pasteles"

¡Y eso es todo! Ahora tienes un sistema completo que funciona tanto como aplicación web como API REST en Laravel 12

Comentarios

Entradas más populares de este blog

crear controladores separados para API y Web

Laravel tanto para web como para API al mismo tiempo.

Creación de una API RESTful con Laravel