La NO guía de REST
Introducción
Hoy en día nos es muy familiar escuchar el termino API REST o directamente ser algo con lo que trabajamos diariamente. En post intentaremos explicar los conceptos detrás de REST, su uso y buenas prácticas. No vamos a profundizar demasiado en lo teórico sino más bien se intentará tener una mirada pragmática.
¿Por qué una API REST?
Antes que nada tenemos que definir el concepto de API. Este término puede ser aplicado en muchos contextos distintos, pero en nuestro caso lo utilizaremos refiriéndonos a una API Web.
Actualmente es algo común trabajar con arquitecturas del tipo cliente-servidor. Donde el cliente realiza distintas peticiones a los servidores y estos le retornan una respuesta.
Básicamente esta API Web definirá de qué manera se comunicarán el cliente y el servidor.
Existen varios tipos distintos de protocolos que sirven para comunicar a los clientes. Uno de los más reconocidos y utilizados en la historia fue SOAP.
SOAP (Simple Object Access Protocol) es un protocolo extremadamente abarcativo y complejo orientado a servicios. Esta complejidad que posee SOAP no aplica en todos los contextos necesarios dentro del desarrollo de software. Entonces surgieron nuevos protocolos que tuvieron mayor éxito en su uso.
En el año 2000 surge REST (REpresentation State Transfer ), definido por Roy Fielding en su disertación de Ph.D. Siendo este un estilo de arquitectura orientado a recursos.
¿Qué es REST?
Tal como se menciona en la sección anterior, REST es un estilo de arquitectura orientada a recursos. ¿Pero qué es un recurso?
Un recurso puede ser cualquier objeto sobre el que la API pueda proporcionar información y se identifique unívocamente . Por ejemplo, en la API de Instagram, un recurso puede ser una foto, un usuario, un video, etc.
Una aplicación web RESTFful expone información sobre sí misma en forma de recursos. También le permite al cliente realizar acciones sobre estos recursos, como crear nuevos recursos, cambiar recursos ya existentes, etc.
Una de las características que posee REST es la facilidad de uso y una rápida curva de aprendizaje.
Restricciones en REST
Si bien REST es un estilo de arquitectura que nos posee muchas ventajas de uso, debe cumplir con ciertas restricciones para considerarse de este estilo.
A continuación se detallaremos estas cuatro restricciones.
Uniform Interface
Esta es quizás la restricción más fuerte que indica REST. Fija las guías para definir las interfaces entre clientes y servidores. Estos se encuentran totalmente desacoplados y pueden evolucionar de manera independiente utilizando dicha interfaz . Para esto se definen las siguientes pautas:
Los recursos requieren un identificador
Todo recurso debe tener un identificador único y es definido mediante una URI la cual nos permite acceder al mismo.
Los recursos se manipulan a través de sus representaciones
Solo se puede interactuar con un recurso a través de su representación, ya sea obteniendo su representación o modificando al recurso por medio de esta.
Mensajes autodescriptivos
Cada mensaje debe incluir la suficiente información para describir cómo procesar el mensaje. Esto se aplica tanto en el request del cliente al servidor como en el response que realiza el servidor.
Hipermedia como motor de estado de la aplicación (HATEOAS)
Toda la representación de un recurso tiene que brindar enlaces para que el cliente pueda explorar y utilizar las acciones vinculadas.
Client-Server
Las interacciones entre el cliente y el servidor siempre son iniciadas por el cliente y el servidor solo responde a dicha demanda. Ambos actúan de manera totalmente independiente.
Brinda la posibilidad que el servidor pueda responder a varios clientes y que los clientes puedan interactuar con distintos servidores.
Stateless
Básicamente indica que todos los requests que se envían desde el cliente hacia el servidor deben contener toda la información necesaria para que el servidor los pueda procesar. Esto permite que sea posible escalar con mayor facilidad ya que no se utilizan sesiones en el servidor.
“El servidor no puede recordar nada sobre quién está utilizando la API”
Layered system
Este requerimiento indica que pueden existir varios servidores intermedios entre el cliente que realiza el request y el servidor que realiza el response. Estas capas intermedias no deben afectar ni al request ni al response y tanto el cliente como el servidor deben ser agnósticos a dichas capas.
Estas capas intermedias se utilizan para brindar mejoras a nivel seguridad, balanceo de carga, etc.
Cacheable
Lo que se define como “cacheabilidad” en los sistemas REST es la capacidad de estos sistemas para etiquetar de alguna forma las respuestas para que otros mecanismos intermedios funcionen como un caché.
Así el sistema puede atender más peticiones, en menos tiempo, con menos recursos (comparado con un sistema sin caché). Pero conlleva un trabajo de mantenimiento de la cache
Code-on-demand
La única restricción que es opcional, e indica que, los servidores pueden ser capaces de aumentar o definir cierta funcionalidad en el cliente transfiriendo cierta lógica que pueda ejecutar
Por ejemplo, ejecución de código JavaScript en el cliente
REST en Web Services
REST no especifica cómo debe implementarse, sino que provee una serie de restricciones y lineamientos. Pero dentro del mundo de los Web Services, la implementación de REST se realiza basándose en el protocolo HTTP
URL
Como habíamos mencionado, los recursos tienen que ser identificados mediante una URI. Dentro de HTTP se utiliza la URL como punto de acceso e identificación de un recurso.
Media Type
El Media Type define la representación del recurso, se especifican con identificadores como text/html, application/json y application/xml, que corresponden a HTML, JSON y XML respectivamente, los formatos web más comunes.
Lo primero que debemos considerar es no implementar nuestro propio tipo de medio personalizado. En cambio, deberíamos elegir uno existente. Los consumidores de API se utilizan para tratar con JSON, XML o sus derivaciones, como JSON API. Esos tipos de medios están estandarizados, lo que significa que tienen herramientas extensas que podemos usar para facilitar el desarrollo de API RESTful.
HTTP
Particularmente los usos de HTTP como protocolo para realizar la implementación de REST están en el uso de sus métodos y sus código de respuesta.
Los verbos HTTP tienen ciertas características que vale la pena destacar
Seguro
Si el método HTTP no altera el estado del servidor (solo realiza una operación de lectura), se lo considera seguro.
Idempotent
Un método HTTP es idempotente si garantiza que en múltiples llamadas el estado del servidor siempre es el mismo, en otras palabras, no tiene efectos secundarios (excepto para mantener estadísticas)
Cacheable
Una respuesta cacheable es una respuesta HTTP que se puede almacenar en caché, que se almacena para recuperarla y usarla más tarde, guardando una nueva solicitud en el servidor. No todas las respuestas HTTP se pueden almacenar en caché. El código de estado de la respuesta es conocido por el almacenamiento en caché de la aplicación, y se considera almacenable en caché
HTTP Methods
Comúnmente los métodos utilizados en una API REST son:
- GET
El método GET solicita una representación de un recurso específico. Las peticiones que usan el método GET sólo deben recuperar datos
- POST
El método POST se utiliza para enviar una entidad a un recurso en específico, causando a menudo un cambio en el estado o efectos secundarios en el servidor.
- PUT
El modo PUT reemplaza todas las representaciones actuales del recurso de destino con la carga útil de la petición.
- DELETE
El método DELETE borra un recurso en específico.
- PATCH
El método PATCH es utilizado para aplicar modificaciones parciales a un recurso.
Pero también HTTP define otros como:
- HEAD
- CONNECT
- OPTIONS
- TRACE
HTTP Verb | Seguro | Idempotente | Cacheable |
---|---|---|---|
GET | X | X | X |
POST | (*) | ||
PUT | X | ||
PATCH | |||
DELETE | X |
(*) Utilizando el header Cache-Control. Solo si se incluye información actualizada
HTTP status codes
Se definen rangos de códigos de estado que indican distintos tipos de respuestas. Se detallarán los más utilizados en un contexto de uso general.
-
Respuestas informativas (100–199)
-
Respuestas satisfactorias (200–299):
-
200 OK:
La solicitud ha tenido éxito. El significado de un éxito varía dependiendo del método HTTP
-
201 Created
La solicitud ha tenido éxito y se ha creado un nuevo recurso como resultado de ello.
-
202 Accepted
La solicitud se ha recibido, pero aún no se ha actuado. Es una petición "sin compromiso", lo que significa que no hay manera en HTTP que permite enviar una respuesta asíncrona que indique el resultado del procesamiento de la solicitud. Está pensado para los casos en que otro proceso o servidor maneja la solicitud, o para el procesamiento por lotes.
-
204 No Response
La petición se ha completado con éxito pero su respuesta no tiene ningún contenido, aunque los encabezados pueden ser útiles. El agente de usuario puede actualizar sus encabezados en caché para este recurso con los nuevos valores.
-
-
Redirecciones (300–399):
-
301 Moved Permanently
Este código de respuesta significa que la URI del recurso solicitado ha sido cambiado. Probablemente una nueva URI sea devuelta en la respuesta.
-
302 Found
Este código de respuesta significa que el recurso de la URI solicitada ha sido cambiado temporalmente. Nuevos cambios en la URI serán agregados en el futuro. Por lo tanto, la misma URI debe ser usada por el cliente en futuras solicitudes.
-
304 Not Modified
Esta es usada para propósitos de "caché". Le indica al cliente que la respuesta no ha sido modificada. Entonces, el cliente puede continuar usando la misma versión almacenada en su caché.
-
-
Errores de los clientes (400–499):
-
400 Bad Request
Esta respuesta significa que el servidor no pudo interpretar la solicitud dada una sintaxis inválida.
-
401 Unauthorized
Es necesario autenticar para obtener la respuesta solicitada. Esta es similar a 403, pero en este caso, la autenticación es posible.
-
403 Forbidden
El cliente no posee los permisos necesarios para cierto contenido, aún siendo correctas las credenciales
-
404 Not Found
El servidor no pudo encontrar el contenido solicitado. Este código de respuesta es uno de los más famosos dada su alta ocurrencia en la web.
-
405 Method not allowed
El usuario intenta violar el contrato de la API, por ejemplo, utilizando un HTTP method para una acción que no esta definida.
-
-
Errores de los servidores (500–599):
-
500 Internal Server Error
El servidor ha encontrado una situación que no sabe cómo manejarla.
-
503 Service Unavailable
El servidor no está listo para manejar la petición. Causas comunes puede ser que el servidor está caído por mantenimiento o está sobrecargado
-
Cross-Origin Resource Sharing (CORS)
same-origin policy (SOP) es una medida de seguridad para evitar usos malintencionados de recursos entre distintos servidores, la cual indica que:
“Todos los datos deben provenir del mismo dominio, es decir, corresponder al mismo servidor.”
Pero…
Puede habilitarse el intercambio de recursos de origen cruzado (CORS) para acceder a recursos de un servidor desde un origen (dominio) distinto al que pertenece
Este mecanismo utiliza distintos headers, los más importantes son:
Por el lado host solicitado
- Access-Control-Allow-Origin: ¿qué origen está permitido?
- Access-Control-Allow-Methods: ¿qué métodos de petición HTTP están permitidos?
Por el lado del host solicitante
- Origin: ¿de qué origen proviene la solicitud?
- Access-Control-Request-Method: ¿qué método de petición HTTP se indica en la solicitud preflight?
Además de una dirección concreta, en dicha cabecera también se puede incluir una wildcard en forma de asterisco. De esta manera, el servidor permitirá cross-origin requests de cualquier origen.
Si bien esta opción está permitida, no es recomendada, ya que al utilizar la wildcard se perdería la validación SOP. Para esto es recomendable configurar CORS lo más restrictivo posible
Richardson Maturity Model
Este modelo, califica a una API en función a cuánto aplica las restricciones de una arquitectura REST. A mayor adherencia a las restricciones, más arriba en la escala se encontrará.
Existen cuatro niveles, pero solo se puede considerar RESTful si se cumplen los requisitos del nivel 3
Nivel 0: The Swamp of POX
Usa el protocolo HTTP como transporte pero no para indicar el estado de la aplicación
Semejante al RPC clásico: SOAP y XML-RPC, en este nivel no existe un marco muy estricto de como se accede a la información
Nivel 1: Resources
Distingue entre diferentes recursos, pero usa un solo método HTTP.
Este nivel ya define una manera mas homogenea como acceder a los datos, no solo bajo que protocolo de transporte, sino ademas como identificar a los distintos recursos. Pero aún no se establece una manera uniforme de como operar con dichos recursos.
Nivel 2: HTTP Verbs
Completo uso de los verbos HTTP combinado con sustantivos de los recursos.
Ya se encuentra definido bajo que tipo de acciones se puede interactuar con los recursos, utilizando los verbos HTTP para cada operación que le corresponda.
Nivel 3: Hypermedia Controls
Uso de HATEOAS (Hypermedia As The Engine Of Application State) para dirigir el estado de la aplicación.
Hasta ahora se tenia definido que protocolo de transporte utilizar y como identificar y operar los recursos, pero todavía se tiene un nivel de acoplamiento muy alto entre el cliente y el servidor, ya que el cliente debe conocer tanto la URL como los métodos permitidos. HATEOAS viene a resolver esta limitación, indicándole al cliente mediante el uso de vínculos, como acceder a los recursos operando con las propiedades de estgos
Buenas prácticas
El diseño de una API REST al fin y al cabo termina siendo una convención entre distintas partes siguiendo los lineamientos que propone dicho estilo. Más allá de este punto de partida, existen distintas prácticas que son recomendadas al momento de diseñar una API.
En términos generales podemos establecer estos conceptos como base para el diseño de la API REST.
- Fácil de entender
- Fácil de integrar.
- Bien documentadas
- Utilizar los estándares aceptados por HTTP
Ahora bien, más allá de estos conceptos hay distintas convenciones que son aceptadas como buenas prácticas.
-
Uso de sustantivos en la URI
Siempre utilizar sustantivos para representar a nuestros recursos. NUNCA verbos o acciones. Ej:
✅
/users
❌
/createUser
-
Plurales o Singulares
Generalmente es más aceptado el uso de URIs en plural, debido a que es más fácil relacionarlo a una colección de recursos. Y si el cliente desea un recurso en particular, deberá conocer su identificador único.
Ej:
- Se retorna una colección de todos los recursos user.
GET /users
[
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
]
Se retorna un recurso User que se identifica con el 123
GET /users/123
{
"id": "123",
"firstname": "Name",
"lastname": "Last"
}
Creación de un recurso User en la colección de Users
POST /users
{
"firstname": "Name",
"lastname": "Last"
}
Responde
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
-
Utilizar las acciones adecuadas para los métodos HTTP
Para realizar acciones sobre los recursos se utilizan los verbos provistos por HTTP. Se deben respetar el uso para los cuales estos verbos fueron diseñados dentro del protocolo.
HTTP Verb | URL | Descripcion |
---|---|---|
GET | /users o /users/123 | Retorna una lista de usuarios o un usuario específico |
POST | /users | Crea un nuevo usuario |
PUT | /users/123 | Actualiza un usuario específico reemplazando toda su representación o lo crea si no existe |
PATCH | /users/123 | Actualiza parcialmente a un usuario específico |
DELETE | /users/123 | Elimina un usuario específico |
-
Realizar un buen uso de los métodos seguros
No utilizar los métodos seguros que provee HTTP para acciones que provoquen cambios en el servidor.
Por ejemplo, NO realizar el delete de la siguiente manera
❌
GET /users/123/delete
-
Representar la jerarquía de recursos a través de URI
Si un recurso contiene sub recursos, se debe representar en la API esta jerarquía.
Por ejemplo, si se desea obtener los posteos de un usuario en un blog
GET /users/123/posts
[
{
"id": "post-id",
"title": "Short title",
"text": "Some text"
}
]
Si se quiere crear un nuevo posteo
POST /users/123/posts
{
"title": "Short title",
"text": "Some text"
}
Pero si se desea obtener un posteo en particular para ese usuario
GET /users/123/posts/1
{
"id": "1",
"title": "Short title",
"text": "Some text"
}
-
Versionar la API
Siempre se debe versionar la API, esto ayuda a mantener retrocompatibilidad en caso de que se agregue alguna nueva funcionalidad o se modifique alguna ya existente.
Comúnmente existen dos maneras de versionar una API
- Headers
-
Utilizando un Custom Header:
Agregando un Custom Header (X-NAME), el cliente indicaría qué versión de la API consumirá. Por ejemplo
-
GET /users
X-VERSION: 1.0.0
[
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
]
-
Utilizando Accept Header
Utilizar el header Accept indicando que versión de la API se va a utilizar. Por ejemplo:
Accept: application/v2+json
[
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
]
-
URL
Incluir el versionado directamente como prefijo en la URL. Por ejemplo,
GET /v1/users
[
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
]
-
Retornar la representación
Al utilizar los métodos HTTP que implican un cambio de estado en el servidor, se deberá retornar la representación del recurso actualizado y el código de estado HTTP que corresponda para esa acción. Por ejemplo para crear un nuevo recurso utilizando el POST,
POST /users
{
"firstname": "Name",
"lastname": "Last"
}
se debe retornar la representación del recurso y el código de estado HTTP 201 en el caso de que no existan errores
{
"id": "user-id",
"firstname": "Name",
"lastname": "Last"
}
201 CREATED
-
Filtrado, búsqueda y ordenamiento
Un uso habitual para el cual se debe diseñar una API es contemplar el filtrado, la búsqueda y el ordenamiento de recursos dentro de una colección. Para esto usos no se recomienda crear diferentes URL, sino mas bien utilizar query parameters.
- Filtrado
Es habitual la necesidad de filtrar una colección por determinados campos. Para esto se recomienda el uso de query params. Estos van concatenados a la URL del recurso como un conjunto de clave=valor
Por ejemplo, queremos obtener la colección de usuarios filtrada por el campo firstname que contenga el string Name y que age sea menor o igual a 10.
GET /users?firstname=Name&age=10
[
{
"firstname": "Name",
"lastname": "Last",
"age": 10
},
{
"firstname": "FirstName",
"lastname": "Other Lastname",
"age": 8
}
]
-
Ordenamiento
Otro caso de uso habitual, es ordenar los resultados de una colección por algún determinado campo. Para esto también se recomienda el uso de query params. Estos van concatenados a la URL del recurso como un conjunto de clave=valor
Por ejemplo, queremos obtener la colección de usuarios ordenada primero por el campo firstname de manera ascendente y en segundo lugar por age descendente.
GET /users?sort=+firstname,-age
[
{
"firstname": "Aaron",
"lastname": "Last",
"age": 10
},
{
"firstname": "Aaron",
"lastname": "Doe",
"age": 8
},
{
"firstname": "John",
"lastname": "Other Lastname",
"age": 8
}
]
-
Paginación
Es habitual la necesidad de filtrar una colección por determinados campos. Para esto se recomienda el uso de query params. Estos van concatenados a la URL del recurso como un conjunto de clave=valor
GET /api/users?limit=10&offset=20
{
"count": 50,
"limit": 10,
"offset": 20,
"results": [
{
"id": 21,
"username": "jdoe",
"email": "[email protected]"
},
{
"id": 22,
"username": "msmith",
"email": "[email protected]"
},
...
{
"id": 30,
"username": "rbrown",
"email": "[email protected]"
}
]
}
-
HATEOAS
HATEOAS, o Hypertext as the Engine of Application State, es un principio fundamental de las API REST que proporciona una forma estandarizada de exponer los recursos y las acciones disponibles a los clientes. En lugar de requerir que los clientes conozcan de antemano todas las URL y acciones disponibles, las API que implementan HATEOAS incluyen enlaces en las respuestas que permiten a los clientes navegar de manera dinámica a través de los recursos y las acciones disponibles. Estos enlaces se construyen como vínculos entre los recursos, de tal manera que el cliente puede descubrir dinámicamente la información necesaria para realizar operaciones específicas en la API sin necesidad de conocer de antemano la estructura de la misma. En esencia, HATEOAS permite a los clientes explorar la API como si fuera un conjunto de recursos interconectados, sin necesidad de conocer de antemano todos los detalles de la estructura y la lógica de la API.
GET /users/1
{
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"links": [
{
"rel": "self",
"href": "/users/1"
},
{
"rel": "update",
"href": "/users/1",
"method": "PUT",
"description": "Update this user"
},
{
"rel": "delete",
"href": "/users/1",
"method": "DELETE",
"description": "Delete this user"
}
]
}
-
Autenticación y autorización stateless
Primero se debe generar una sesion mediante el uso del endpoint definido para dicha accion. La respuesta de esto debe ser un
POST /login HTTP/1.1
Content-Type: application/json
{
"email": "[email protected]",
"password": "password"
}
Y la respuesta seria
HTTP/1.1 200 OK
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
Luego para acceder a un recurso protegido se debe enviar el token en el Header Authorization
GET /users HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-
Documentación
swagger
Ejemplo
Herramientas
curl
postman
insomnia
advanced rest client
Recursos
- https://www.oreilly.com/content/how-a-restful-api-represents-resources/#:~:text=Common%20Media%20Types%20for%20RESTful,the%20most%20common%20web%20formats.
- https://medium.com/hashmapinc/rest-good-practices-for-api-design-881439796dc9
- https://martinfowler.com/articles/richardsonMaturityModel.html
- https://cloud.google.com/blog/products/gcp/api-design-which-version-of-versioning-is-right-for-you