Agustín Rojas

La NO guía de REST

a logo

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

a logo

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