Módulo 5 · Lección 1 25 min

API REST con Laravel

Aprende a construir una API REST completa con Laravel: rutas, controladores, autenticación y buenas prácticas para devolver datos JSON.

¿Qué es una API REST?

Una API REST (Representational State Transfer) es un conjunto de convenciones para construir servicios web que permiten la comunicación entre sistemas a través del protocolo HTTP. En lugar de devolver HTML como lo hace una aplicación web tradicional, una API REST devuelve datos en formato JSON (o XML), que pueden ser consumidos por aplicaciones móviles, frontends en React/Vue, otros servidores o cualquier cliente HTTP.

Laravel es uno de los frameworks más populares para construir APIs REST gracias a su elegancia, su potente sistema de rutas y sus herramientas integradas como Eloquent, API Resources y Sanctum.

Principios básicos de REST

Antes de escribir código, conviene entender las convenciones que rigen una API REST bien diseñada:

  • Recursos: Todo se representa como un recurso identificado por una URL. Por ejemplo, /api/productos representa la colección de productos.
  • Métodos HTTP: Cada operación usa el verbo HTTP adecuado:
    • GET /api/productos — listar todos los productos
    • POST /api/productos — crear un nuevo producto
    • GET /api/productos/{id} — obtener un producto específico
    • PUT /api/productos/{id} — actualizar un producto
    • DELETE /api/productos/{id} — eliminar un producto
  • Sin estado (stateless): Cada petición debe contener toda la información necesaria. El servidor no guarda sesiones entre peticiones.
  • Códigos de estado HTTP: La API debe responder con el código correcto: 200 OK, 201 Created, 404 Not Found, 422 Unprocessable Entity, etc.

Crear el proyecto y configurar la API

Si partes desde cero, crea un proyecto Laravel:

composer create-project laravel/laravel mi-api
cd mi-api

Las rutas de la API van en routes/api.php. A diferencia de routes/web.php, estas rutas usan el middleware api por defecto, que no incluye protección CSRF y está optimizado para peticiones sin estado.

Definir las rutas

// routes/api.php

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

Route::apiResource('productos', ProductoController::class);

Route::apiResource() genera automáticamente estas rutas:

MétodoURIAcciónNombre
GET/api/productosindexproductos.index
POST/api/productosstoreproductos.store
GET/api/productos/{producto}showproductos.show
PUT/PATCH/api/productos/{producto}updateproductos.update
DELETE/api/productos/{producto}destroyproductos.destroy

Puedes verificar las rutas generadas con:

php artisan route:list

Crear el modelo y la migración

php artisan make:model Producto -m

Define la migración:

// database/migrations/xxxx_create_productos_table.php

public function up(): void
{
    Schema::create('productos', function (Blueprint $table) {
        $table->id();
        $table->string('nombre');
        $table->text('descripcion')->nullable();
        $table->decimal('precio', 8, 2);
        $table->integer('stock')->default(0);
        $table->timestamps();
    });
}

Ejecuta la migración:

php artisan migrate

Y define los campos rellenables en el modelo:

// app/Models/Producto.php

class Producto extends Model
{
    protected $fillable = ['nombre', 'descripcion', 'precio', 'stock'];
}

Crear el controlador API

php artisan make:controller Api/ProductoController --api

El flag --api genera un controlador con los cinco métodos REST sin create ni edit (que son para formularios HTML). Implementa cada método:

// app/Http/Controllers/Api/ProductoController.php

namespace App\Http\Controllers\Api;

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

class ProductoController extends Controller
{
    public function index(): JsonResponse
    {
        $productos = Producto::all();
        return response()->json($productos);
    }

    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'nombre'      => 'required|string|max:255',
            'descripcion' => 'nullable|string',
            'precio'      => 'required|numeric|min:0',
            'stock'       => 'required|integer|min:0',
        ]);

        $producto = Producto::create($validated);

        return response()->json($producto, 201);
    }

    public function show(Producto $producto): JsonResponse
    {
        return response()->json($producto);
    }

    public function update(Request $request, Producto $producto): JsonResponse
    {
        $validated = $request->validate([
            'nombre'      => 'sometimes|string|max:255',
            'descripcion' => 'nullable|string',
            'precio'      => 'sometimes|numeric|min:0',
            'stock'       => 'sometimes|integer|min:0',
        ]);

        $producto->update($validated);

        return response()->json($producto);
    }

    public function destroy(Producto $producto): JsonResponse
    {
        $producto->delete();
        return response()->json(null, 204);
    }
}

Manejo de errores en la API

Laravel devuelve HTML por defecto cuando ocurre un error. Para que la API siempre devuelva JSON, tienes dos opciones. En Laravel 11 puedes registrar un handler en bootstrap/app.php:

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (\Illuminate\Validation\ValidationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Los datos enviados no son válidos.',
                'errors'  => $e->errors(),
            ], 422);
        }
    });

    $exceptions->render(function (\Illuminate\Database\Eloquent\ModelNotFoundException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json(['message' => 'Recurso no encontrado.'], 404);
        }
    });
})

Probar la API con cURL

# Listar productos
curl -X GET http://localhost:8000/api/productos \
  -H "Accept: application/json"

# Crear un producto
curl -X POST http://localhost:8000/api/productos \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"nombre":"Teclado mecánico","precio":89.99,"stock":50}'

# Actualizar un producto
curl -X PUT http://localhost:8000/api/productos/1 \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"precio":79.99}'

# Eliminar un producto
curl -X DELETE http://localhost:8000/api/productos/1 \
  -H "Accept: application/json"

Paginación

En lugar de devolver todos los registros con Producto::all(), usa paginación para no sobrecargar la respuesta:

public function index(): JsonResponse
{
    $productos = Producto::paginate(15);
    return response()->json($productos);
}

Laravel incluye automáticamente metadatos de paginación en la respuesta JSON:

{
  "data": [...],
  "current_page": 1,
  "last_page": 4,
  "per_page": 15,
  "total": 58,
  "next_page_url": "http://localhost:8000/api/productos?page=2",
  "prev_page_url": null
}

Buenas prácticas

  • Versiona tu API: Prefija tus rutas con /api/v1/ para poder hacer cambios sin romper clientes existentes.
  • Usa API Resources: En la siguiente lección aprenderás a formatear las respuestas JSON de forma controlada.
  • Valida siempre: Nunca confíes en los datos del cliente. Usa el sistema de validación de Laravel.
  • Documenta: Herramientas como Scribe o Laravel OpenAPI generan documentación automática a partir de tu código.
  • Añade autenticación: Una API pública sin autenticación es un riesgo. Usa Laravel Sanctum (para SPAs y móvil) o Passport (para OAuth2).

Con estos fundamentos ya puedes construir una API REST funcional en Laravel. En las próximas lecciones profundizaremos en formatear las respuestas con API Resources, proteger los endpoints con autenticación y optimizar el rendimiento con caché.

Quiz

Pon a prueba lo aprendido

Responde las preguntas para comprobar que has entendido los conceptos clave.

1. ¿Qué método HTTP se usa convencionalmente para crear un nuevo recurso en una API REST?

2. ¿En qué archivo de Laravel se definen las rutas de una API?

3. ¿Qué comando de Artisan genera un controlador con todos los métodos REST (index, store, show, update, destroy)?