Qué significa que una API sea RESTful
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), y el matiz importa. REST no es una interfaz HTTP: es un estilo de arquitectura, definido por Roy Fielding en su tesis doctoral (2000), que impone 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, normalmente a través de HTTP. Está tan integrado en el desarrollo web, y estandarizado, que si no lo vemos nos resulta raro.
Y aquí viene la parte incómoda: en rigor, REST ya incluye todo lo que vas a leer en este artículo. La hipermedia forma parte de la restricción de interfaz uniforme, y "RESTful" es simplemente el adjetivo, "conforme a REST". Lo que ha pasado es que el uso popular ha ido degradando "REST" hasta significar "API HTTP que devuelve JSON", tanto que el propio Fielding publicó en 2008 un célebre artículo, REST APIs must be hypertext-driven, quejándose de que llamamos REST a cualquier RPC disfrazado de HTTP. El Richardson Maturity Model le puso números a esa distancia: nivel 0, un único endpoint tipo RPC; nivel 1, recursos con URI propia; nivel 2, verbos y códigos de estado HTTP bien usados; nivel 3, hipermedia. La inmensa mayoría de las APIs "REST" que consumes a diario se quedan en el nivel 2.
En este artículo usaré RESTful para referirme a ese nivel 3, el que Fielding exige. 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 hipermedia (hypermedia) 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": {
"self": {
"href": "/users/123",
"method": "GET"
},
"update": {
"href": "/users/123",
"method": "PUT"
},
"delete": {
"href": "/users/123",
"method": "DELETE"
},
"friends": {
"href": "/users/123/friends",
"method": "GET"
},
"posts": {
"href": "/users/123/posts",
"method": "GET"
},
"search": {
"href": "/search/?query={query}",
"method": "GET",
"templated": true
}
}
}
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 (la clave del objeto), 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.
Observa el uso de templated: true en el último enlace. Esto indica que el href contiene una plantilla URI (siguiendo RFC 6570) que debe ser completada con valores específicos antes de usarse. Es una forma elegante de descubrir endpoints parametrizados. Por ejemplo, el cliente podría reemplazar {query} con "api restful" para buscar contenido relacionado.
Un apunte de honestidad antes de continuar: la convención _links viene de HAL (Hypertext Application Language), pero HAL no define el campo method; sus enlaces solo contemplan propiedades como href, templated, type o title. Lo añado porque en la práctica ayuda a que la API sea autodescriptiva, y es una extensión habitual. Si necesitas acciones con métodos y campos formalizados, mira Siren; si prefieres HAL puro, omite method y confía en las convenciones del protocolo (regla 2).
No todos los recursos necesitan tener hipermedia, 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
Estas reglas no me las invento: son las seis condiciones que Fielding enumera en el artículo que mencioné antes, adaptadas aquí con ejemplos.
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 reemplazar, PATCH para actualizaciones parciales 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. Es la regla peor entendida de las seis, y para Fielding es donde debe irse casi todo el esfuerzo descriptivo. La idea: en vez de publicar un listado de rutas (lo que solemos llamar "la documentación"), defines y documentas tus tipos de media (por ejemplo application/vnd.myshop.product+json), es decir, qué significan los campos de cada representación y cómo se procesan sus enlaces. El cliente decide qué hacer según el Content-Type que recibe, no según la URI que ha llamado. Las URIs pasan a ser detalles intercambiables.
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": {
"self": {
"href": "/users/123",
"method": "GET",
"type": "application/json"
},
"friends": {
"href": "/users/123/friends",
"method": "GET",
"type": "text/csv"
},
"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": "Totoro plush",
"price": 19.99,
"_links": {
"self": {
"href": "/products/987",
"method": "GET"
},
"addToCart": {
"href": "/cart/add/987",
"method": "POST"
},
"reviews": {
"href": "/products/987/reviews",
"method": "GET"
}
}
}
Y al día siguiente:
{
"id": 987,
"name": "Totoro plush",
"price": 19.99,
"_links": {
"self": {
"href": "/shop/item/987",
"method": "GET"
},
"addToCart": {
"href": "/shop/cart/987",
"method": "POST"
},
"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": "Welcome to my RESTful API",
"_links": {
"users": {
"href": "/users",
"method": "GET"
},
"products": {
"href": "/products",
"method": "GET"
},
"orders": {
"href": "/orders",
"method": "GET"
},
"search": {
"href": "/search/?query={query}&page={page}",
"method": "GET",
"templated": true
},
"user-by-id": {
"href": "/users/{user_id}",
"method": "GET",
"templated": true
},
"product-search": {
"href": "/products/search/?name={name}&category={category}",
"method": "GET",
"templated": true
}
}
}
Fíjate en que varios enlaces usan templated: true, las plantillas URI que vimos en la sección de HATEOAS: el cliente descubre hasta los endpoints parametrizados sin salir de la respuesta.
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.
FAQ
¿Cómo indico la versión de la API o endpoint?
Lo más común es incluir la versión en la URL, por ejemplo: /api/v1/users. Sin embargo, si quieres seguir las reglas de RESTful, deberías sacar la versión de la URI. Lo más recomendado es negociarla con el encabezado Accept usando la estructura application/vnd.example.v1+json, donde example es el nombre de tu API y v1 es la versión.
Por ejemplo, mi API se llama dream y la última versión es la 2.1, entonces el encabezado sería: Accept: application/vnd.dream.v2.1+json.
Otra opción más sencilla, aunque menos recomendada, es usar un encabezado personalizado como Api-Version, donde el valor sería 2.1. Eso sí, evita el clásico prefijo X- (X-API-Version): está desaconsejado desde 2012 por la RFC 6648.
¿Cómo manejo la paginación?
Necesitarás incluir metadatos en la respuesta para indicar la paginación con meta.
Un ejemplo de paginación podría ser:
{
"data": [...],
"meta": {
"total": 150,
"page": 1,
"perPage": 10,
"hasNext": true,
"hasPrevious": false
},
"_links": {
"self": {"href": "/api/items?page=1", "method": "GET"},
"next": {"href": "/api/items?page=2", "method": "GET"},
"first": {"href": "/api/items?page=1", "method": "GET"},
"last": {"href": "/api/items?page=15", "method": "GET"}
}
}
Donde:
total: Total de elementos disponibles. No en la página actual, sino en toda la colección.page: Número de la página actual. Nunca puede ser 0 o negativo.perPage: Número de elementos por página.hasNext: Indica si hay más páginas disponibles.hasPrevious: Indica si hay páginas anteriores disponibles._links: Enlaces para navegar por la paginación, comonext,previous,first,lastyself. Cuando un enlace no aplica (no hay página anterior en la primera página), se omite, no se envía anull: así lo hace HAL y evita ambigüedades al cliente.
También puedes usar plantillas para permitir al cliente especificar el número de página:
{
"_links": {
"page": {
"href": "/api/items?page={page}&perPage={perPage}",
"method": "GET",
"templated": true
}
}
}
¿Cómo gestiono los errores?
Aquí sí hay estándar, aunque poca gente lo conoce: la RFC 9457 (Problem Details for HTTP APIs, que actualiza la RFC 7807 de 2016). Define el tipo de media application/problem+json con cinco campos: type (URI que identifica la categoría del problema), title (resumen corto y estable), status (código HTTP), detail (explicación de esta ocurrencia concreta) e instance (URI del recurso afectado). Y permite añadir campos de extensión propios.
Por ejemplo, una respuesta con estado 404 Not Found y el encabezado Content-Type: application/problem+json llevaría este cuerpo:
{
"type": "https://example.com/errors/article-not-found",
"title": "Article not found",
"status": 404,
"detail": "There is no article with id c9bb4e4a",
"instance": "/api/blog/c9bb4e4a"
}
Si empiezas una API desde cero, mi consejo es que uses Problem Details: es el estándar y tus clientes lo agradecerán. En mis proyectos uso una convención propia (type, errors, data y meta) que se integra con la estructura de respuesta de mis casos de uso; si te pica la curiosidad, la explico en Implementando arquitectura limpia en Python.
Conclusiones
Una API RESTful de verdad, la que Fielding describió, no es una evolución de REST: es REST completo, el nivel 3 del Richardson Maturity Model que casi nadie alcanza. Mientras el uso popular se queda en recursos, verbos y códigos de estado, la hipermedia añade lo que convierte una API en una interfaz autodescriptiva y navegable.
Para los errores ya tienes estándar, la RFC 9457: úsala y tus clientes sabrán siempre qué esperar.
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:
- Roy Fielding's Dissertation
- REST APIs must be hypertext-driven por Roy Fielding.
- Richardson Maturity Model por Martin Fowler.
- RFC 9457: Problem Details for HTTP APIs
- HAL - Hypertext Application Language
- REST Architectural Constraints por Lokesh Gupta.
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.
- HATEOAS (Hypermedia as the Engine of Application State)
- Las 6 reglas de una API RESTful
- 1. No dependas de un solo protocolo
- 2. No cambies el protocolo
- 3. Céntrate en los tipos de media, no en URIs
- 4. No asumas estructuras URI
- 5. Evita "Tipos" de Recursos
- 6. Comienza con un marcador (recurso raíz) y deja que el cliente explore
- FAQ
- ¿Cómo indico la versión de la API o endpoint?
- ¿Cómo manejo la paginación?
- ¿Cómo gestiono los errores?
- Conclusiones
- Fuentes
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Will you buy me a coffee?
This is how I keep writing without ads or paywalls.
Sure, it's on me!
Comments
There are no comments yet.