Saltar al contenido

Si trabajas con APIs que consume un frontend (Vue, React, móvil...), probablemente has visto código así en el cliente:

// ❌ El frontend construye la URL
const response = await fetch(`${API_URL}/users/${userId}`);

Parece inofensivo, pero hay un problema: el frontend está asumiendo cómo son las rutas del backend. Si mañana cambias /users/{id} por /api/v2/users/{id}, tienes que tocar el frontend.

HATEOAS propone una solución elegante: que el backend devuelva las URLs y el frontend solo las consuma.

¿Qué es HATEOAS?

HATEOAS son las siglas de "Hypermedia As The Engine Of Application State". El nombre es rimbombante, pero la idea es simple: cada respuesta de tu API incluye las URLs de las acciones disponibles para ese recurso.

El frontend no construye URLs, las descubre a través de las respuestas.

Un ejemplo práctico

Imagina una API que devuelve un pedido:

{
    "data": {
        "id": 1,
        "status": "pending",
        "total": 150.00
    },
    "urls": {
        "self": {
            "href": "https://api.example.com/orders/1",
            "method": "GET"
        },
        "pay": {
            "href": "https://api.example.com/orders/1/pay",
            "method": "POST"
        },
        "cancel": {
            "href": "https://api.example.com/orders/1/cancel",
            "method": "POST"
        }
    }
}

El frontend recibe esta respuesta y sabe exactamente:

  • Dónde está el recurso (self)

  • Cómo pagar el pedido (pay)

  • Cómo cancelarlo (cancel)

No necesita construir ninguna URL. Solo consume lo que el backend le da.

Las URLs cambian según el estado

Aquí está la magia de HATEOAS. Si el pedido ya está pagado, la respuesta cambia:

{
    "data": {
        "id": 1,
        "status": "paid",
        "total": 150.00
    },
    "urls": {
        "self": {
            "href": "https://api.example.com/orders/1",
            "method": "GET"
        },
        "invoice": {
            "href": "https://api.example.com/orders/1/invoice",
            "method": "GET"
        }
    }
}

Ya no aparece pay ni cancel porque no tienen sentido en este estado. En su lugar aparece invoice.

El frontend puede mostrar u ocultar botones simplemente comprobando si la URL existe:

// ✅ El frontend solo comprueba si la acción está disponible
if (order.urls.cancel) {
    showCancelButton(order.urls.cancel);
}

La lógica de negocio (cuándo se puede cancelar un pedido) queda en el backend, donde debe estar.

Implementación en Laravel

En Laravel puedes implementar esto fácilmente con API Resources:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class OrderResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'data' => [
                'id' => $this->id,
                'status' => $this->status,
                'total' => $this->total,
            ],
            'urls' => $this->getAvailableUrls(),
        ];
    }

    private function getAvailableUrls(): array
    {
        $urls = [
            'self' => [
                'href' => route('orders.show', $this->resource),
                'method' => 'GET',
            ],
        ];

        if ($this->canBePaid()) {
            $urls['pay'] = [
                'href' => route('orders.pay', $this->resource),
                'method' => 'POST',
            ];
        }

        if ($this->canBeCancelled()) {
            $urls['cancel'] = [
                'href' => route('orders.cancel', $this->resource),
                'method' => 'POST',
            ];
        }

        if ($this->isPaid()) {
            $urls['invoice'] = [
                'href' => route('orders.invoice', $this->resource),
                'method' => 'GET',
            ];
        }

        return $urls;
    }
}

Los métodos canBePaid(), canBeCancelled() e isPaid() estarían en tu modelo Order, encapsulando la lógica de negocio.

Ventajas de este enfoque

El frontend no conoce las rutas

Si cambias /orders/{id}/pay por /orders/{id}/payment, el frontend no se entera. Sigue funcionando porque consume la URL que le llega.

La lógica de negocio queda en el backend

¿Cuándo se puede cancelar un pedido? ¿Hasta qué hora? ¿Solo si no ha sido enviado? Todo eso lo decide el backend. El frontend solo muestra el botón si la URL existe.

Menos bugs por desincronización

Cuando frontend y backend están desacoplados de verdad, hay menos posibilidades de que uno asuma algo que el otro ha cambiado.

Documentación implícita

Las URLs disponibles documentan qué puede hacer el cliente en cada momento. Es una API autodescriptiva.

Conclusión

HATEOAS es un principio simple pero potente: el backend es el dueño de las URLs, y el frontend solo las consume.

No necesitas implementar una especificación completa como JSON:API o HAL. Basta con que tus respuestas incluyan las URLs de las acciones disponibles con su método HTTP correspondiente.

Tu frontend te lo agradecerá.

school Curso completo

Curso Laravel 12
Completo 2026

El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.

access_time 8 horas de contenido
layers 4 tecnologías en 1
update 100% actualizado
code Proyectos prácticos
Ver Curso Laravel 12 arrow_forward

star Incluido en cualquier suscripción

Rutas de aprendizaje

Newsletter

Recibe nuevos cursos, actualizaciones, artículos del blog y promociones en tu correo.