Construyendo APIs RESTful robustas con Arquitectura Limpia y Hypermedia

Lo siento, pero no sabes qué es una API RESTful. Sé que estás curtido integrando APIs de todo tipo, y hasta puede ser que tengas en producción decenas en multitud de lenguajes. Pero todo ello solo demuestra que has seguido buenas prácticas del sector y has leído inconmensurables líneas de documentación, lo cual es bueno. Sin embargo, nada de lo anterior certifica tus conocimientos sobre RESTful. ¡No te agobies!, no estás solo. La mayoría de los desarrolladores que conozco tampoco sabrían definir, implementar o señalar sus puntos fuertes. Por eso te voy a explicar en este artículo sus virtudes con ejemplos sencillos de entender. Y hasta puede ser que cuando termines el artículo no vuelvas a ver una API de la misma forma. ¡Avisado estás!

En muchas ocasiones se usa API RESTful como sinónimo de API REST (Representational State Transfer), pero no son lo mismo. Por un lado REST es una interfaz HTTP que define restricciones:

  • Arquitectura cliente-servidor: El cliente y el servidor deben estar separados.
  • Sin estado (stateless): Cada solicitud del cliente al servidor debe contener toda la información necesaria para entender y procesar la solicitud. Es decir, el servidor no guardará información sobre el estado del cliente entre solicitudes.
  • Cacheable: Las respuestas deben ser explícitamente marcadas como cacheables o no cacheables.
  • Sistema en capas: La arquitectura puede estar compuesta por capas, donde cada capa tiene una función específica y estarán aisladas entre sí.
  • Interfaz uniforme: La comunicación entre el cliente y el servidor debe ser predecible, con un patrón bien definido.
  • Código bajo demanda (opcional): El servidor puede enviar código ejecutable al cliente, como scripts JavaScript, para extender la funcionalidad del cliente.

En otras palabras, REST es un conjunto de principios arquitectónicos para servir recursos a través de HTTP. Está tan integrado en el desarrollo web, y estandarizado, que si no lo vemos nos resulta raro.

Por otro lado tenemos API RESTful, un conjunto de reglas por encima de REST que deben cumplirse para que una API sea considerada RESTful (REST + ful de full o completo). El objetivo es transformar una API en una interfaz predecible, estandarizada y autodescriptiva. Solo con la URL base, el cliente podrá explorar todos los recursos, sin necesidad de recurrir a documentación externa. Además, dispondremos de la flexibilidad de poder cambiar las rutas sin afectar al cliente, o incluso jugar con varios protocolos de comunicación.

Pero antes hay un concepto que debes conocer: HATEOAS (Hypermedia as the Engine of Application State). Este concepto es fundamental para entender cómo una API RESTful puede ser autodescriptiva y navegable.

HATEOAS (Hypermedia as the Engine of Application State)

La hypermedia (o hipermedia) es una de las características clave de RESTful, permite a los clientes descubrir dinámicamente los recursos a través de enlaces (links) proporcionados en las respuestas. Suelen estar bajo el padre _links.

{
    "id": 123,
    "name": "John Doe",
    "_links": [
        {
            "rel": "self",
            "href": "/users/123",
            "method": "GET"
        },
        {
            "rel": "update",
            "href": "/users/123",
            "method": "PUT"
        },
        {
            "rel": "delete",
            "href": "/users/123",
            "method": "DELETE"
        },
        {
            "rel": "friends",
            "href": "/users/123/friends",
            "method": "GET"
        },
        {
            "rel": "posts",
            "href": "/users/123/posts",
            "method": "GET"
        }
    ]
}

Gracias a ello, los clientes pueden navegar por la API parseando y siguiendo los enlaces para llegar hasta la información que necesitan, similar a como navegarías por internet. Además, como saltas entre las rutas relativas (href) usando sus nombres como identificador (rel), el backend podría cambiar las direcciones sin que ello afecte al cliente. Es muy poderoso porque permite que el cliente no esté atado a una estructura fija de rutas, se mueve entre nodos.

No todos los recursos necesitan tener hypermedia, solo los relevantes con un contexto adecuado. Por ejemplo, no tendría sentido incluir en un artículo de blog los enlaces a un carrito de compra, pero sí sería interesante que en un artículo estuviera presente el enlace al autor, los comentarios o artículos relacionados.

Si ya entendemos la importancia de HATEOAS, veamos las reglas que debe cumplir una API RESTful.

Las 6 reglas de una API RESTful

1. No dependas de un solo protocolo

Utiliza identificadores de recursos (URIs) para definir los recursos. En otras palabras, en lugar de utilizar una URL (http://example.com/api/users/123), usa un URI que indique su ubicación e ignore el protocolo (/users/123). De este modo podrías utilizar otros como WebSocket, MQTT, NNTP, RPC, etc. Claro que puedes usar HTTP, pero no debes depender solo de él. Por ejemplo, si tu API está diseñada para funcionar exclusivamente sobre HTTP, no sería estrictamente RESTful.

2. No cambies el protocolo

No reinventes la rueda. No te pongas en modo creativo con los protocolos. Por ejemplo, si usas HTTP, sigue las convenciones: GET para obtener recursos, POST para crear, PUT para actualizar y DELETE para eliminar. Usa los estados adecuados de respuesta HTTP: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, etc. ¿Usas MQTT? Usa los comandos y estados adecuados.

3. Céntrate en los tipos de media, no en URIs

En lugar de documentar cada URI externamente, debes hacer que tu API describa los tipos de media que maneja.

Por ejemplo, el recurso de un usuario con poca información podría ser:

{
    "id": 123,
    "name": "John Doe",
    "_links": 
        {
            "self": "/users/123",
            "friends": "/users/123/friends",
            "lastInvoice": "/users/123/invoice/last"
        }
}

Mientras que el recurso de un usuario con más información podría ser:

{
    "id": 123,
    "name": "John Doe",
    "_links": 
        [
            {
                "rel": "self",
                "href": "/users/123",
                "method": "GET",
                "type": "application/json"
            },
            {
                "rel": "friends",
                "href": "/users/123/friends",
                "method": "GET",
                "type": "text/csv"
            },
            {
                "rel": "lastInvoice",
                "href": "/users/123/invoice/last",
                "method": "GET",
                "type": "application/pdf"
            }
        ]
}

4. No asumas estructuras URI

No debes guardar o reutilizar las estructuras de las URIs en el cliente. Una API podría cambiarla sin previo aviso, y obviamente tu cliente dejaría de funcionar. En su lugar, parsea los enlaces y sigue los identificadores relativos (rel).

Un recurso puede tener la siguiente estructura:

{
    "id": 987,
    "name": "Muñeco de Totoro",
    "price": 19.99,
    "_links": [
        {
            "rel": "self",
            "href": "/products/987",
            "method": "GET"
        },
        {
            "rel": "addToCart",
            "href": "/cart/add/987",
            "method": "POST"
        },
        {
            "rel": "reviews",
            "href": "/products/987/reviews",
            "method": "GET"
        }
    ]
}

Y al día siguiente:

{
    "id": 987,
    "name": "Muñeco de Totoro",
    "price": 19.99,
    "_links": [
        {
            "rel": "self",
            "href": "/shop/item/987",
            "method": "GET"
        },
        {
            "rel": "addToCart",
            "href": "/shop/cart/987",
            "method": "POST"
        },
        {
            "rel": "reviews",
            "href": "/shop/item/987/reviews",
            "method": "GET"
        }
    ]
}

5. Evita "Tipos" de Recursos

No expongas la jerarquía, permisos o tipos de recursos en la API. Al cliente ni le importa ni debe saberlo. Por ejemplo, no deberías tener un recurso /users/admin/123 o /users/guest/123. En su lugar, utiliza los enlaces para definir las acciones que se pueden realizar sobre el recurso.

6. Comienza con un marcador (recurso raíz) y deja que el cliente explore

Los clientes deben empezar con una URL raíz (o marcador), a partir de ahí irán navegando por tu API-verso.

{
    "message": "Bienvenido a mi RESTful API",
    "_links": [
        {
            "rel": "users",
            "href": "/users",
            "method": "GET"
        },
        {
            "rel": "products",
            "href": "/products",
            "method": "GET"
        },
        {
            "rel": "orders",
            "href": "/orders",
            "method": "GET"
        }
    ]
}

Ya tienes una idea general de cómo construir una API RESTful. Entre los principios REST, las reglas de API RESTful y el concepto de HATEOAS, podrás crear una sólida API.

Lamentablemente no hay un estándar de cómo mostrar los errores, qué formato usar o cómo estructurar la información. Aquí nos puede ayudar la arquitectura limpia.

Arquitectura Limpia

Si quieres saber más sobre la arquitectura limpia, te recomiendo que leas mi artículo Implementando arquitectura limpia en Python. Aquí voy a quedarme con una pequeña parte: la estructura de la respuesta.

Podemos dar más información al cliente, como el tipo de respuesta, errores, datos y metadatos. Por ejemplo:

{
    "type": "Success",
    "errors": [],
    "data": [...],
    "meta": {
        "total": 150,
        "page": 1,
        "per_page": 10,
        "has_next": true,
        "links": {
            "next": "http://andros.internal/api/blog/?page=2",
            "last": "http://andros.internal/api/blog/?page=15"
        }
    }
}
  • type: Tipo de respuesta, puede ser Success, ParametersError, ResourceError y SystemError.
  • errors: Lista de errores, si los hay. Cada error puede tener un campo field (campo afectado) y message (mensaje descriptivo).
  • data: Datos de la respuesta, o la estructura que hemos estado viendo hasta ahora.
  • meta: Metadatos de la respuesta, como el total de elementos, la página actual, el número de elementos por página y enlaces para la paginación. Podrías considerarlo como un mini HATEOAS para la paginación.

Si quiero listar los artículos de un blog, la respuesta podría ser algo así:

{
    "type": "Success",
    "errors": [],
    "data": [
        {
            "id": "c9bb4e4a",
            "title": "Construyendo APIs RESTful robustas",
            "publicationDate": "2025-07-01T12:00:00Z",
            "content": "Lorem ipsum",
            "_links": [
                {
                    "rel": "self",
                    "href": "/api/blog/c9bb4e4a",
                    "method": "GET"
                },
                {
                    "rel": "edit",
                    "href": "/api/blog/c9bb4e4a/edit",
                    "method": "PUT"
                },
                {
                    "rel": "delete",
                    "href": "/api/blog/c9bb4e4a/delete",
                    "method": "DELETE"
                },
                {
                    "rel": "author",
                    "href": "/api/authors/123",
                    "method": "GET"
                }
            ]
        }
    ],
    "meta": {
        "total": 150,
        "page": 1,
        "per_page": 10,
        "has_next": true,
        "_links": {
            "next": "/api/blog/?page=2",
            "last": "/api/blog/?page=15"
        }
    }
}

En cambio, si hay un error en los parámetros, como puede ser obtener un artículo con una ID no válida, la respuesta podría ser:

{
    "type": "ParametersError",
    "errors": [
        {
            "field": "id",
            "message": "La ID del artículo no es válida"
        }
    ],
    "data": {},
    "meta": {}
}

También podrías recibir errores si no envías los parámetros necesarios o adecuados.

{
    "type": "ParametersError",
    "errors": [
        {
            "field": "name",
            "message": "El parámetro 'name' es obligatorio"
        },
        {
            "field": "amount",
            "message": "El parámetro 'amount' debe ser un número positivo"
        }
    ],
    "data": {},
    "meta": {}
}

Tal vez estás intentando acceder a un PDF que no existe, entonces la respuesta podría ser:

{
    "type": "ResourceError",
    "errors": [
        {
            "field": "pdf",
            "message": "El PDF solicitado no existe o el sistema no puede acceder a él"
        }
    ],
    "data": {},
    "meta": {}
}

Y si hay un error del sistema, como una base de datos caída, la respuesta podría ser:

{
    "type": "SystemError",
    "errors": [
        {
            "field": "database",
            "message": "Error crítico en el sistema, no se puede acceder a la base de datos"
        }
    ],
    "data": {},
    "meta": {}
}

Aunque la gran parte de las ocasiones recibirás un type de Success o ParametersError.

Conclusiones

Las APIs RESTful representan una evolución a las APIs REST tradicionales. Mientras que REST establece los pilares arquitectónicos, RESTful añade las reglas para crear interfaces autodescriptivas y navegables.

Por otro lado, si lo combinas con una arquitectura limpia para estructurar las respuestas (type, errors, data y meta) podrás gestionar diferentes tipos de errores de manera consistente.

En definitiva, una API RESTful bien implementada no solo transfiere datos, sino que lleva de la mano al cliente por todos sus recursos, creando una experiencia intuitiva y resiliente al cambio. Lo cual es esencial para un proyecto a largo plazo.

Fuentes

Si quieres seguir profundizando en el tema, aquí tienes algunas fuentes recomendadas:

Además sería interesante que leyeras más respecto a HAL y JSON-LD, ya que son formatos que facilitan la implementación de HATEOAS y la autodescripción de las APIs RESTful.

Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.

¿Me invitas a un café?

Puedes usar el terminal.

ssh customer@andros.dev -p 5555

Escrito por Andros Fenollosa

julio 22, 2025

8 min de lectura

Sigue leyendo