Sistema CRUD API REST en Laravel 12
Sistema CRUD API REST en Laravel 12
Tutorial completo para crear una API REST con Laravel 12, basado en el tutorial original pero enfocado únicamente en el desarrollo de API.
📚 Índice
Configuración inicial
Creación de la migración
Creación del modelo
Creación del Factory y Seeder
Creación del Controlador API
Definición de Rutas API
Pruebas de la API
Manejo de errores y respuestas
Comandos útiles
1️⃣ Configuración inicial
1.1 Requisitos previos
# Verifica tu versión de PHP (mínimo 8.2 para Laravel 12)
php -v
# Verifica tu versión de Composer
composer -V1.2 Crear el proyecto
# Crea un nuevo proyecto Laravel
composer create-project laravel/laravel pasteles-api
cd pasteles-api
# Instalar dependencias adicionales (opcional pero recomendado para API)
composer require laravel/sanctum1.3 Configurar la base de datos
Edita el archivo .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=pasteles_api_db
DB_USERNAME=root
DB_PASSWORD=1.4 Configurar Sanctum (para autenticación API)
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate2️⃣ Creación de la migración
2.1 Generar la migración
php artisan make:migration create_pasteles_table2.2 Definir la estructura de la tabla
database/migrations/[timestamp]_create_pasteles_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
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);
$table->boolean('disponible')->default(true);
$table->text('descripcion')->nullable();
$table->timestamps();
$table->softDeletes(); // Eliminación lógica
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pasteles');
}
};2.3 Ejecutar la migración
php artisan migrate3️⃣ Creación del modelo
3.1 Generar el modelo
php artisan make:model Pastel3.2 Definir el modelo
app/Models/Pastel.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;
/**
* La tabla asociada al modelo.
*/
protected $table = 'pasteles';
/**
* Los atributos que son asignables masivamente.
*/
protected $fillable = [
'nombre',
'sabor',
'precio',
'disponible',
'descripcion'
];
/**
* Los atributos que deben ser convertidos a tipos nativos.
*/
protected $casts = [
'precio' => 'decimal:2',
'disponible' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
];
/**
* Los atributos que deben ser ocultados para arrays/JSON.
*/
protected $hidden = [
'deleted_at'
];
/**
* Los atributos que deben ser incluidos en arrays/JSON.
*/
protected $visible = [
'id',
'nombre',
'sabor',
'precio',
'disponible',
'descripcion',
'created_at',
'updated_at'
];
/**
* Scope para obtener solo pasteles disponibles.
*/
public function scopeDisponibles($query)
{
return $query->where('disponible', true);
}
/**
* Scope para obtener pasteles por sabor.
*/
public function scopeBySabor($query, $sabor)
{
return $query->where('sabor', $sabor);
}
/**
* Scope para obtener pasteles por rango de precio.
*/
public function scopePrecioEntre($query, $min, $max)
{
return $query->whereBetween('precio', [$min, $max]);
}
/**
* Accesor para formatear el precio.
*/
public function getPrecioFormateadoAttribute(): string
{
return '$' . number_format($this->precio, 2);
}
/**
* Mutator para capitalizar el nombre.
*/
public function setNombreAttribute($value): void
{
$this->attributes['nombre'] = ucwords(strtolower($value));
}
}4️⃣ Creación del Factory y Seeder
4.1 Generar el Factory
php artisan make:factory PastelFactorydatabase/factories/PastelFactory.php
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Pastel>
*/
class PastelFactory extends Factory
{
/**
* Define el estado predeterminado del modelo.
*
* @return array<string, mixed>
*/
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), // 80% de probabilidad
'descripcion' => $this->faker->sentence(10),
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
];
}
/**
* Estado para pasteles no disponibles.
*/
public function noDisponible(): static
{
return $this->state(fn (array $attributes) => [
'disponible' => false,
]);
}
/**
* Estado para pasteles de chocolate.
*/
public function chocolate(): static
{
return $this->state(fn (array $attributes) => [
'sabor' => 'chocolate',
]);
}
}4.2 Generar el Seeder
php artisan make:seeder PastelSeederdatabase/seeders/PastelSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Pastel;
class PastelSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Crear 50 pasteles con el factory
Pastel::factory(50)->create();
// Crear 5 pasteles de chocolate no disponibles
Pastel::factory(5)->chocolate()->noDisponible()->create();
// Crear 10 pasteles de chocolate disponibles
Pastel::factory(10)->chocolate()->create();
$this->command->info('✅ Se crearon ' . Pastel::count() . ' pasteles en la base de datos.');
}
}4.3 Registrar el Seeder
database/seeders/DatabaseSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
PastelSeeder::class,
]);
}
}4.4 Ejecutar migraciones y seeders
# Ejecutar migraciones y seeders juntos
php artisan migrate --seed
# O por separado
php artisan migrate
php artisan db:seed5️⃣ Creación del Controlador API
5.1 Generar el controlador
php artisan make:controller Api/PastelController5.2 Definir el controlador API
app/Http/Controllers/Api/PastelController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Pastel;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class PastelController extends Controller
{
/**
* GET /api/pasteles
* Lista todos los pasteles con opción de filtros.
*/
public function index(Request $request)
{
try {
$query = Pastel::query();
// Filtro por disponibilidad
if ($request->has('disponible')) {
$query->where('disponible', filter_var($request->disponible, FILTER_VALIDATE_BOOLEAN));
}
// Filtro por sabor
if ($request->has('sabor')) {
$query->bySabor($request->sabor);
}
// Filtro por rango de precio
if ($request->has('precio_min') && $request->has('precio_max')) {
$query->precioEntre($request->precio_min, $request->precio_max);
}
// Búsqueda por nombre
if ($request->has('search')) {
$query->where('nombre', 'LIKE', '%' . $request->search . '%');
}
// Paginación
$perPage = $request->input('per_page', 15);
$pasteles = $query->paginate($perPage);
return response()->json([
'success' => true,
'data' => $pasteles->items(),
'meta' => [
'current_page' => $pasteles->currentPage(),
'per_page' => $pasteles->perPage(),
'total' => $pasteles->total(),
'last_page' => $pasteles->lastPage(),
],
'message' => 'Pasteles obtenidos exitosamente'
], 200);
} catch (\Exception $e) {
return $this->errorResponse('Error al obtener los pasteles', 500);
}
}
/**
* POST /api/pasteles
* Crea un nuevo pastel.
*/
public function store(Request $request)
{
try {
// Validación de datos
$validated = $request->validate([
'nombre' => 'required|string|max:100',
'sabor' => ['required', Rule::in(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'])],
'precio' => 'required|numeric|min:0|max:999999.99',
'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);
} catch (\Illuminate\Validation\ValidationException $e) {
return $this->errorResponse('Error de validación', 422, $e->errors());
} catch (\Exception $e) {
return $this->errorResponse('Error al crear el pastel', 500);
}
}
/**
* GET /api/pasteles/{id}
* Muestra un pastel específico.
*/
public function show($id)
{
try {
$pastel = Pastel::findOrFail($id);
return response()->json([
'success' => true,
'data' => $pastel,
'message' => 'Pastel encontrado'
], 200);
} catch (ModelNotFoundException $e) {
return $this->errorResponse('Pastel no encontrado', 404);
} catch (\Exception $e) {
return $this->errorResponse('Error al obtener el pastel', 500);
}
}
/**
* PUT /api/pasteles/{id}
* Actualiza un pastel completo.
*/
public function update(Request $request, $id)
{
try {
$pastel = Pastel::findOrFail($id);
$validated = $request->validate([
'nombre' => 'sometimes|string|max:100',
'sabor' => ['sometimes', Rule::in(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'])],
'precio' => 'sometimes|numeric|min:0|max:999999.99',
'disponible' => 'sometimes|boolean',
'descripcion' => 'nullable|string|max:1000',
]);
$pastel->update($validated);
return response()->json([
'success' => true,
'data' => $pastel->fresh(),
'message' => 'Pastel actualizado exitosamente'
], 200);
} catch (ModelNotFoundException $e) {
return $this->errorResponse('Pastel no encontrado', 404);
} catch (\Illuminate\Validation\ValidationException $e) {
return $this->errorResponse('Error de validación', 422, $e->errors());
} catch (\Exception $e) {
return $this->errorResponse('Error al actualizar el pastel', 500);
}
}
/**
* DELETE /api/pasteles/{id}
* Elimina un pastel (Eliminación lógica).
*/
public function destroy($id)
{
try {
$pastel = Pastel::findOrFail($id);
$pastel->delete();
return response()->json([
'success' => true,
'message' => 'Pastel eliminado exitosamente'
], 200);
} catch (ModelNotFoundException $e) {
return $this->errorResponse('Pastel no encontrado', 404);
} catch (\Exception $e) {
return $this->errorResponse('Error al eliminar el pastel', 500);
}
}
/**
* GET /api/pasteles/disponibles
* Obtiene solo los pasteles disponibles.
*/
public function disponibles()
{
try {
$pasteles = Pastel::disponibles()->get();
return response()->json([
'success' => true,
'data' => $pasteles,
'message' => 'Pasteles disponibles obtenidos exitosamente'
], 200);
} catch (\Exception $e) {
return $this->errorResponse('Error al obtener los pasteles disponibles', 500);
}
}
/**
* GET /api/pasteles/sabor/{sabor}
* Obtiene pasteles por sabor.
*/
public function bySabor($sabor)
{
try {
$saboresValidos = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
if (!in_array($sabor, $saboresValidos)) {
return $this->errorResponse('Sabor no válido', 400);
}
$pasteles = Pastel::bySabor($sabor)->get();
if ($pasteles->isEmpty()) {
return $this->errorResponse('No se encontraron pasteles del sabor ' . $sabor, 404);
}
return response()->json([
'success' => true,
'data' => $pasteles,
'message' => 'Pasteles del sabor ' . $sabor . ' obtenidos exitosamente'
], 200);
} catch (\Exception $e) {
return $this->errorResponse('Error al obtener los pasteles por sabor', 500);
}
}
/**
* GET /api/pasteles/estadisticas
* Obtiene estadísticas de los pasteles.
*/
public function estadisticas()
{
try {
$total = Pastel::count();
$disponibles = Pastel::disponibles()->count();
$noDisponibles = $total - $disponibles;
$precioPromedio = Pastel::avg('precio');
$precioMaximo = Pastel::max('precio');
$precioMinimo = Pastel::min('precio');
// Agrupar por sabor
$porSabor = Pastel::selectRaw('sabor, count(*) as total, avg(precio) as promedio')
->groupBy('sabor')
->get();
return response()->json([
'success' => true,
'data' => [
'total' => $total,
'disponibles' => $disponibles,
'no_disponibles' => $noDisponibles,
'precio_promedio' => round($precioPromedio, 2),
'precio_maximo' => round($precioMaximo, 2),
'precio_minimo' => round($precioMinimo, 2),
'distribucion_por_sabor' => $porSabor
],
'message' => 'Estadísticas obtenidas exitosamente'
], 200);
} catch (\Exception $e) {
return $this->errorResponse('Error al obtener estadísticas', 500);
}
}
/**
* POST /api/pasteles/bulk
* Crea múltiples pasteles en una sola petición.
*/
public function bulkStore(Request $request)
{
try {
$validated = $request->validate([
'pasteles' => 'required|array|min:1|max:100',
'pasteles.*.nombre' => 'required|string|max:100',
'pasteles.*.sabor' => ['required', Rule::in(['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'])],
'pasteles.*.precio' => 'required|numeric|min:0',
'pasteles.*.disponible' => 'sometimes|boolean',
'pasteles.*.descripcion' => 'nullable|string|max:1000',
]);
$creados = [];
foreach ($validated['pasteles'] as $data) {
if (!isset($data['disponible'])) {
$data['disponible'] = true;
}
$creados[] = Pastel::create($data);
}
return response()->json([
'success' => true,
'data' => $creados,
'message' => count($creados) . ' pasteles creados exitosamente'
], 201);
} catch (\Illuminate\Validation\ValidationException $e) {
return $this->errorResponse('Error de validación', 422, $e->errors());
} catch (\Exception $e) {
return $this->errorResponse('Error al crear los pasteles', 500);
}
}
/**
* Método auxiliar para respuestas de error.
*/
private function errorResponse($message, $code, $errors = null)
{
$response = [
'success' => false,
'message' => $message
];
if ($errors) {
$response['errors'] = $errors;
}
return response()->json($response, $code);
}
}6️⃣ Definición de Rutas API
6.1 Configurar rutas
routes/api.php
<?php
use App\Http\Controllers\Api\PastelController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
*/
// Rutas públicas (sin autenticación)
Route::prefix('pasteles')->group(function () {
// CRUD básico
Route::get('/', [PastelController::class, 'index']); // GET /api/pasteles
Route::post('/', [PastelController::class, 'store']); // POST /api/pasteles
Route::get('/{id}', [PastelController::class, 'show']); // GET /api/pasteles/1
Route::put('/{id}', [PastelController::class, 'update']); // PUT /api/pasteles/1
Route::delete('/{id}', [PastelController::class, 'destroy']); // DELETE /api/pasteles/1
// Rutas adicionales
Route::get('/disponibles', [PastelController::class, 'disponibles']); // GET /api/pasteles/disponibles
Route::get('/sabor/{sabor}', [PastelController::class, 'bySabor']); // GET /api/pasteles/sabor/chocolate
Route::get('/estadisticas', [PastelController::class, 'estadisticas']); // GET /api/pasteles/estadisticas
// Bulk operations
Route::post('/bulk', [PastelController::class, 'bulkStore']); // POST /api/pasteles/bulk
});6.2 Ver las rutas
php artisan route:list7️⃣ Pruebas de la API
7.1 Usando cURL (desde la terminal)
# GET - Obtener todos los pasteles
curl -X GET http://localhost:8000/api/pasteles
# GET - Con paginación
curl -X GET "http://localhost:8000/api/pasteles?per_page=5&page=2"
# GET - Con filtros
curl -X GET "http://localhost:8000/api/pasteles?disponible=true&sabor=chocolate"
# GET - Búsqueda por nombre
curl -X GET "http://localhost:8000/api/pasteles?search=Pastel"
# GET - Obtener un pastel específico
curl -X GET http://localhost:8000/api/pasteles/1
# POST - Crear un pastel
curl -X POST http://localhost:8000/api/pasteles \
-H "Content-Type: application/json" \
-d '{
"nombre": "Pastel de Chocolate Premium",
"sabor": "chocolate",
"precio": 350.00,
"disponible": true,
"descripcion": "El mejor pastel de chocolate de toda la ciudad"
}'
# PUT - Actualizar un pastel
curl -X PUT http://localhost:8000/api/pasteles/1 \
-H "Content-Type: application/json" \
-d '{
"precio": 400.00,
"disponible": false
}'
# DELETE - Eliminar un pastel
curl -X DELETE http://localhost:8000/api/pasteles/1
# GET - Pasteles disponibles
curl -X GET http://localhost:8000/api/pasteles/disponibles
# GET - Pasteles por sabor
curl -X GET http://localhost:8000/api/pasteles/sabor/chocolate
# GET - Estadísticas
curl -X GET http://localhost:8000/api/pasteles/estadisticas
# POST - Crear múltiples pasteles
curl -X POST http://localhost:8000/api/pasteles/bulk \
-H "Content-Type: application/json" \
-d '{
"pasteles": [
{
"nombre": "Pastel de Fresa",
"sabor": "fresa",
"precio": 280.00,
"disponible": true,
"descripcion": "Delicioso pastel de fresa"
},
{
"nombre": "Pastel de Vainilla",
"sabor": "vainilla",
"precio": 250.00,
"disponible": true,
"descripcion": "Clásico pastel de vainilla"
}
]
}'7.2 Usando Postman
Importar colección: Crea una colección llamada "Pasteles API"
Variables de entorno: Configura
{{base_url}}=http://localhost:8000
Endpoints para Postman:
| Método | URL | Descripción |
|---|---|---|
| GET | {{base_url}}/api/pasteles | Listar pasteles |
| GET | {{base_url}}/api/pasteles?disponible=true | Filtrar disponibles |
| GET | {{base_url}}/api/pasteles?search=chocolate | Buscar por nombre |
| POST | {{base_url}}/api/pasteles | Crear pastel |
| GET | {{base_url}}/api/pasteles/1 | Ver pastel |
| PUT | {{base_url}}/api/pasteles/1 | Actualizar pastel |
| DELETE | {{base_url}}/api/pasteles/1 | Eliminar pastel |
| GET | {{base_url}}/api/pasteles/disponibles | Pasteles disponibles |
| GET | {{base_url}}/api/pasteles/sabor/chocolate | Pasteles por sabor |
| GET | {{base_url}}/api/pasteles/estadisticas | Estadísticas |
| POST | {{base_url}}/api/pasteles/bulk | Crear múltiples |
7.3 Usando PHPUnit (Pruebas automáticas)
# Crear un test para la API
php artisan make:test PastelApiTesttests/Feature/PastelApiTest.php
<?php
namespace Tests\Feature;
use App\Models\Pastel;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PastelApiTest extends TestCase
{
use RefreshDatabase;
public function test_can_get_all_pasteles()
{
Pastel::factory(3)->create();
$response = $this->getJson('/api/pasteles');
$response->assertStatus(200)
->assertJsonStructure([
'success',
'data',
'meta',
'message'
]);
}
public function test_can_create_pastel()
{
$data = [
'nombre' => 'Pastel de Prueba',
'sabor' => 'chocolate',
'precio' => 299.99,
'disponible' => true,
'descripcion' => 'Descripción de prueba'
];
$response = $this->postJson('/api/pasteles', $data);
$response->assertStatus(201)
->assertJson([
'success' => true,
'message' => 'Pastel creado exitosamente'
]);
$this->assertDatabaseHas('pasteles', [
'nombre' => 'Pastel de Prueba'
]);
}
public function test_can_show_pastel()
{
$pastel = Pastel::factory()->create();
$response = $this->getJson("/api/pasteles/{$pastel->id}");
$response->assertStatus(200)
->assertJson([
'success' => true,
'data' => [
'id' => $pastel->id
]
]);
}
public function test_can_update_pastel()
{
$pastel = Pastel::factory()->create();
$response = $this->putJson("/api/pasteles/{$pastel->id}", [
'precio' => 500.00,
'disponible' => false
]);
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Pastel actualizado exitosamente'
]);
$this->assertDatabaseHas('pasteles', [
'id' => $pastel->id,
'precio' => 500.00,
'disponible' => false
]);
}
public function test_can_delete_pastel()
{
$pastel = Pastel::factory()->create();
$response = $this->deleteJson("/api/pasteles/{$pastel->id}");
$response->assertStatus(200)
->assertJson([
'success' => true,
'message' => 'Pastel eliminado exitosamente'
]);
$this->assertSoftDeleted('pasteles', [
'id' => $pastel->id
]);
}
public function test_can_get_disponibles()
{
Pastel::factory(3)->create(['disponible' => true]);
Pastel::factory(2)->create(['disponible' => false]);
$response = $this->getJson('/api/pasteles/disponibles');
$response->assertStatus(200)
->assertJsonCount(3, 'data');
}
public function test_can_get_by_sabor()
{
Pastel::factory(2)->create(['sabor' => 'chocolate']);
Pastel::factory(3)->create(['sabor' => 'vainilla']);
$response = $this->getJson('/api/pasteles/sabor/chocolate');
$response->assertStatus(200)
->assertJsonCount(2, 'data');
}
public function test_returns_404_for_nonexistent_pastel()
{
$response = $this->getJson('/api/pasteles/999');
$response->assertStatus(404)
->assertJson([
'success' => false,
'message' => 'Pastel no encontrado'
]);
}
public function test_validation_fails_for_invalid_data()
{
$response = $this->postJson('/api/pasteles', [
'nombre' => '', // Nombre vacío
'sabor' => 'invalido',
'precio' => -100
]);
$response->assertStatus(422)
->assertJson([
'success' => false,
'message' => 'Error de validación'
]);
}
}7.4 Ejecutar los tests
php artisan test --filter=PastelApiTest8️⃣ Manejo de errores y respuestas
8.1 Estructura de respuestas exitosas
{
"success": true,
"data": {
// Datos del recurso
},
"message": "Mensaje descriptivo"
}8.2 Estructura de respuestas con paginación
{
"success": true,
"data": [
// Array de recursos
],
"meta": {
"current_page": 1,
"per_page": 15,
"total": 50,
"last_page": 4
},
"message": "Pasteles obtenidos exitosamente"
}8.3 Estructura de respuestas de error
{
"success": false,
"message": "Mensaje de error",
"errors": {
// Errores de validación (opcional)
}
}8.4 Códigos de estado HTTP utilizados
| Código | Significado | Uso |
|---|---|---|
| 200 | OK | Operación exitosa |
| 201 | Created | Recurso creado exitosamente |
| 400 | Bad Request | Solicitud incorrecta |
| 401 | Unauthorized | No autenticado |
| 403 | Forbidden | No autorizado |
| 404 | Not Found | Recurso no encontrado |
| 422 | Unprocessable Entity | Error de validación |
| 500 | Internal Server Error | Error del servidor |
9️⃣ Comandos útiles
9.1 Comandos de migración
# Ejecutar migraciones
php artisan migrate
# Reiniciar y ejecutar migraciones con seeders
php artisan migrate:fresh --seed
# Ver estado de migraciones
php artisan migrate:status
# Revertir última migración
php artisan migrate:rollback9.2 Comandos de limpieza
# 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:list9.3 Comandos de desarrollo
# Iniciar servidor de desarrollo
php artisan serve
# Generar clave de aplicación
php artisan key:generate
# Crear un controlador
php artisan make:controller Api/NombreController
# Crear un modelo con migración y factory
php artisan make:model Nombre -m -f
# Crear un seeder
php artisan make:seeder NombreSeeder📌 Resumen de endpoints
| Método | URL | Descripción |
|---|---|---|
| GET | /api/pasteles | Listar pasteles (con filtros) |
| POST | /api/pasteles | Crear pastel |
| GET | /api/pasteles/{id} | Obtener pastel |
| PUT | /api/pasteles/{id} | Actualizar pastel |
| DELETE | /api/pasteles/{id} | Eliminar pastel |
| GET | /api/pasteles/disponibles | Pasteles disponibles |
| GET | /api/pasteles/sabor/{sabor} | Pasteles por sabor |
| GET | /api/pasteles/estadisticas | Estadísticas |
| POST | /api/pasteles/bulk | Crear múltiples pasteles |
🔒 Autenticación (Opcional)
Si quieres proteger tu API con autenticación:
# Instalar Sanctum
composer require laravel/sanctum
# Publicar configuración
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
# Migrar
php artisan migrateEn el modelo User:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}En routes/api.php:
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('pasteles', PastelController::class);
});Para obtener token:
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'Usar token:
curl -X GET http://localhost:8000/api/pasteles \
-H "Authorization: Bearer TOKEN_AQUI"¡Y eso es todo! Ahora tienes una API REST completa en Laravel 12 con todas las operaciones CRUD, filtros, paginación, validaciones y manejo de errores. 🚀
La API está lista para ser consumida por aplicaciones frontend (React, Vue, Angular), aplicaciones móviles o cualquier otro cliente que soporte JSON
Comentarios
Publicar un comentario