Saltar al contenido

Resumen Completo de API REST

¿Qué es una API?

API es el acrónimo de Application Programming Interface, o Interfaz de Programación de Aplicaciones.

Es un conjunto de funciones o métodos que un sistema expone al exterior para que otros sistemas puedan invocarlos.

Tipos de API

  • API de Sistema Operativo: permite acceder a los recursos que gestiona el sistema operativo, como los discos duros, las redes, etc. (ejemplo: Win32 API de Windows o Linux Kernel API).
  • API de Librería: módulo con funciones, clases y métodos reutilizables por varios programas (ejemplo: archivo DLL como System.dll o System.Web.Mvc.dll).
  • API Remota: permite acceder a recursos de un sistema, con la misma plataforma, a través de una red (ejemplo: RPC de Linux o WCF).
  • API Web: también llamado servicio web, permite acceder a recursos de un sistema vía HTTP. No es necesario que sea de la misma plataforma.

Tipos de API Web

  • SOAP: está basado en XML, lleva a cabo peticiones tipo RPC y es orientado a la conexión. Está obsoleto y se ha ido sustituyendo por las otras 3 que se comentan a continuación.
  • REST: basado en JSON, lleva a cabo peticiones tipo HTTP y no está orientado a la conexión o a mantener estados. Cada petición es independiente.
  • GraphQL: muy parecido a REST, pero es más potente ay que permite realizar peticiones más complejas al definir un lenguaje similar a SQL. Se suele usar para agregar diversas fuentes de datos y exponer una interfaz única sobre la que realizar consultas.
  • gRPC: está basado en Protobuf y requiere de peticiones tipo HTTP2. Es decir, permite la comunicación bidireccional. Se suele usar también para streaming.

API REST Básica

Mensaje de Petición HTTP

El mensaje de petición consiste en:

  1. Un verbo HTTP.
  2. La URL del recurso.
  3. La versión de HTTP (son típicas HTTP/1.1 o HTTP/2).
  4. Varias cabeceras HTTP.
  5. Un cuerpo (opcional).

Un primer ejemplo sería:

GET /api/usuarios/ HTTP1.1
Host: webapi.com
Accept: application/json
Accept-Language: es-es

Las peticiones GET, que permiten obtener información, no tienen cuerpo. La primera línea se compone de verbo HTTP (GET) seguido de la URL del recurso (/api/usuarios) y termina con la versión HTTP (HTTP/1.1). Las otras 3 líneas son 3 cabeceras HTTP, aunque podría llevar bastantes más.

Otro ejemplo sería:

POST /api/usuarios HTTP1.1
Host: webapi.com
Content-Type: text/plain { "nombre": "Francisco Serrano", "email": "f.serrano@gmail.com" }

Las peticiones POST, que permiten enviar información, poseen un cuerpo con los datos. Debe haber una línea de separación entre las cabeceras y el cuerpo.

Mensaje de Respuesta HTTP

Un mensaje de respuesta consiste en:

  1. La versión de HTTP.
  2. Un código numérico de respuesta.
  3. Un texto descriptivo de respuesta.
  4. Varias cabeceras HTTP.
  5. Un cuerpo (en el caso de API REST suele ser JSON en vez de HTML).

Por ejemplo:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: es-es

{
  "nombre": "Francisco Serrano",
  "email": "f.serrano@gmail.com"
}

La primera línea contiene la versión HTTP (HTTP/1.1), el código numérico de respuesta (200) y un texto descriptivo del mismo (OK) indicando que todo ha ido bien. A continuación hay 2 líneas con cabeceras HTTP, una línea en blanco y el cuerpo con la respuesta en formato JSON.

Verbos HTTP

Los principales verbos HTTP son 4:

  • GET: permite obtener los datos de un recurso o de una lista de recursos. Nunca lleva cuerpo.
  • POST: permite crear un nuevo recurso enviando sus datos como el cuerpo de la petición.
  • PUT: permite actualizar al completo los datos de un recurso existente. De forma opcional, en caso de que no exista, lo puede crear.
  • DELETE: permite eliminar un recurso existente. Tampoco lleva cuerpo.

Otros verbos menos usados son:

  • PATCH: permite actualizar parcialmente los datos de un recurso existente. Lo recomendable es usar el estándar JSON PATCH, también conocido por RFC 6902.
  • HEAD: permite saber si existe un recurso, ya que es igual que un GET pero en la respuesta no viene cuerpo.
  • OPTIONS: permite conocer qué tipos de verbos HTTP admite un servidor.
  • TRACE: se usa en depuración, para saber si una petición HTTP está llegando al servidor.

Una característica muy importante de los verbos HTTP es la idempotencia, que significa que, si se aplica una acción (petición HTTP) varias veces, el resultado debe ser el mismo.

Todos los verbos HTTP son idempotentes salvo POST y PATCH.

Estructura de las URL

Cada recurso del sistema debe tener una URL única.

Las partes que se consideran obligatorias son:

  1. /api al principio, para indicar que es la API REST del dominio. Como alternativa, se puede usar un subdominio (es decir, api.dominio.com en vez de dominio.com/api).
  2. /versión a continuación, indicando la versión de la API, normalmente con un número. Es común no modificar una API (lo que afectaría a los sistema que hacen uso de ella) sino crear nuevas versiones (ejemplo: dominio.com/api/v2).
  3. /entidad para indicar el nombre del recurso con el que trabajar (ejemplo: dominio.com/api/v1/usuarios). De forma opcional, se pueden jerarquizar mediante categorías (ejemplo: dominio.com/api/v1/productos/ofertas).

A partir de ahí, se pueden agregar datos opcionales, pero siempre en el siguiente orden:

  1. /id número que identifica a un recurso de forma única (ejemplo: dominio.com/api/v1/usuarios/33).
  2. /subentidad es una entidad que depende de la anterior (ejemplo: dominio.com/api/v1/productos/123/comentarios/).
  3. /idSubentidad número que identifica a la subentidad de forma única (eje. dominio.com/api/v1/productos/123/comentarios/4567). Estas subentidades no podrían existir por sí mismas, dependen de sus entidades, de forma que, si se borra la entidad, también hay que borrar sus subentidades.
  4. ?filtro1=valor1&filtro2=valor2 son parámetros en formato «query string» que permite enviar filtros de búsqueda en las peticiones GET (ejemplo: dominio.com/api/v1/usuarios?activos=true&ciudad=Madrid). En caso de que hubiera muchos parámetros de búsqueda, se puede usar un POST en vez del GET y pasarlos en el cuerpo del mensaje.

Algo muy importante en las API REST es que se debe usar sustantivos para nombrar los recursos y evitar al máximo el uso de verbos (algunos diseñadores de API no admiten verbos en ningún caso).

Hay desarrolladores que siempre usan el plural para nombrar a sus entidad, tal y como ves en los ejemplos, pero otros prefieren usar el singular cuando se refieren a una única entidad (por ejemplo, usar dominio.com/api/v1/usuario/33 en vez de dominio.com/api/v1/usuarios/33).

Códigos de Respuesta HTTP

Existen muchísimos códigos HTTP de respuesta, agrupados en categorías (según el número con el que comienzan), pero en API REST se suelen usar tan solo unos pocos:

  • Respuestas 1XX: son informativos y no se usan en API REST.
  • Respuestas 2XX: indican que todo ha ido bien. Los que más se usan en API REST son:
    • 200 OK: lo devuelve el GET. Indican que todo ha ido bien. Devuelve los datos de la entidad solicitada o una lista de entidades con sus datos.
    • 201 Created: lo devuelve el POST, ya que indica que la entidad se ha creado correctamente. También devuelve la URL del nuevo recurso.
    • 202 Accepted: pueden devolverlo los POST y los PUT. Indica que la petición ha sido aceptada, pero está pendiente de procesarse. Esto evita que el cliente se quede a la espera demasiado tiempo, sobre todo cuando son procesos largos que se pueden ejecutar en segundo plano.
    • 204 No Content: lo devuelve el DELETE y el PUT. Indica que fue bien, pero no devuelve nada más.
  • Respuestas 3XX: tienen que ver con redirecciones HTTP y no se deben usar en API REST.
  • Respuestas 4XX: indican que ha ocurrido algún error de cliente, es decir, que la petición HTTP no es correcta. Los que más se usan en API REST son:
    • 400 Bad Request: la petición está mal formada o con datos incorrectos (RFC 7807).
    • 401 Unauthorized: el usuario debe autenticarse para acceder al recurso.
    • 403 Forbidden: la autenticación falló y no se puede acceder al recurso.
    • 404 Not Found: el recurso solicitado no existe (no usar nunca con “query strings”).
    • 405 Method Not Allowed: la entidad indicada no admite ese verbo HTTP.
    • 422 Unprocessable Entity: indica que, por alguna razón, no se puede llevar a cabo la acción con esa entidad.
  • Respuesta 5XX: indican que ha ocurrido algún error de servidor, es decir, la petición HTTP es correcta pero el servidor ha fallado al procesarla y generar una respuesta. El más común es el 500 Internal Server Error.

API REST Avanzada

Swagger / OpenAPI

Es el estándar de facto para documentar API REST. Se implementa en un archivo JSON cuyo esquema está especificado en https://swagger.io/specification/.

La mayoría de los lenguajes de programación más usados permiten definirlo a partir del código. Por ejemplo, en .NET 6 existe la librería Swashbuckel.AspNetCore.

También se puede hacer lo contrario: definir el archivo JSON con la especificación de una API y, a partir del mismo, generar el código de la API.

Versionado

Si no implementas versiones en tus API, vas a tener problemas serios. Las API van a tenerse que modificar o ampliar antes o después. Si modificas una API que está ya en producción, estarías obligando a todos tus clientes a implementar dichos cambios (y no todos lo van a poder hacer a la vez).

Una mejor estrategia es sacar diferentes versiones de tu API. Así, los clientes podrán seguir usando la antigua hasta que tengan tiempo de pasarse a la nueva (si es que lo desean, porque podrían seguir usando la antigua mientras que tú no la elimines).

La forma más común de indicar la versión es en la propia URL, mediante algún número que se vaya incrementado:

dominio.com/api/v1/usuarios
dominio.com/api/v2/usuarios
dominio.com/api/v3/usuarios

Otra forma menos común de hacerlo, pero más transparente, es enviando la información en alguna cabecera HTTP:

GET /api/usuarios HTTP/1.1
Version: 1

Esta tiene la ventaja de que es más fácil de cambiar el código de cliente cuando se decide usar una nueva versión de la API. Se podría centralizar en un middleware que se encargara de agregar dicha cabecera a todas las peticiones HTTP que se hagan a la API.

Lo que no te recomiendo en ningún caso es que lo hagas mediante query strings (dominio.com/api/usuarios?version=1). Es una mala práctica que da muchos problemas a futuro.

Mejorar el Rendimiento de una API REST

  • Usar Asincronía: cada vez que tengas que acceder a un recurso de entrada y salida (por ejemplo: acceso a archivo en disco, consulta a base de datos, petición a otra API, etc.), es muy recomendable el uso de async / await. Esto permite que el programa no se quede esperando por la respuesta, sino que continua con otras tareas hasta que el recurso contesta.
  • Usar Cachés: hay informaciones que no cambian a cada momento (por ejemplo: el nombre de los países o las provincias) y no tiene sentido tener que estar consultándolas a base de datos cada vez que las solicita un cliente. Lo mejor es meterlas en alguna caché el tiempo que se considere oportuno.
  • Usar Cuotas: no sería bueno que un único usuario monopolice el API. Una buena práctica es establecer un número máximo de peticiones que puede realizar un mismo usuario en un tiempo determinado (por ejemplo: máximo 1.000 peticiones al día).
  • Usar Escalado Horizontal: para evitar saturar a los servidores y recursos, es recomendable hacer un estudio de cuántas peticiones como máximo puede soportar el sistema. En caso de alcanzarse esos límites, se puede estudiar soluciones de escalado dinámico horizontal (por ejemplo, si el API está en un contenedor Docker, se puede crear más contenedores con la misma API para dar soporte al pico de trabajo, y eliminarlos cuando la cantidad de peticiones baje).

HATEOAS

Es el acrónimo de Hypermedia As The Engine Of Application State. Detrás de este nombre tan extraño está una técnica que es bastante sencilla y que suele dar buenos resultados. Pero antes, algo de contexto.

Hay ocasiones en las que interesa devolver, no solo los datos asociados a una entidad, sino también la de ciertos (o todos) sus recursos relacionados.

{
  "id": 1,
  "titulo": "Un Gran Consejo",
  "autor":
  {
    "id": 33,
    "nombre": "Fernando García",
    "email": "f.garcia@dominio.com",
    "role": "Administrador"
  },
  "fecha": "2022-10-09T12:45:00",
  "contenido": "Este es el artículo ...",
  "Comentarios":
  [
    {
      "Id": 444,
      "Autor":
      {
        "id": 67,
        "nombre": "Marta Rodríguez",
        "email": "marta.rodriguez@gmail.com",
        "role": "Lector"
      },
      "Mensaje": "Me ha gustado mucho y es muy útil"
    },
    {
      "Id": 445,
      "Autor":
      {
        "id": 42,
        "nombre": "Sonia Pérez",
        "email": "sonia.perez@hotmail.com",
        "role": "Lector"
      },
      "Mensaje": "Voy a recomendárselo a todo el mundo"
    }
  ]
}

Esto prueba provocar problemas de rendimiento, porque no solo tienes que ir a buscar la información de todos esos recursos relacionados, sino que el tamaño del mensaje devuelto es mayor. La cosas se agrava si luego el cliente no hace uso de todas la información devuelta y solo de parte de ella.

La alternativa que propone HATEOAS es que, en vez de devolver los datos de los recursos relacionados, se devuelve la URL de dichos recursos para que, en caso de necesitarse, se invoque directamente a dichas URL, facilitándole la vida a los clientes pero sin provocar problemas de rendimiento:

{
  "id": 1,
  "titulo": "Un Gran Consejo",
  "autor": "/api/v1/usuarios/33",
  "fecha": "2022-10-09T12:45:00",
  "contenido": "Este es el artículo ...",
  "Comentarios":
  [
    "/api/v1/articulos/1/comentarios/444",
    "/api/v1/articulos/1/comentarios/445"
  ]
}

Desacoplar Servicios

Cuando en tu sistema tienes varias API (por ejemplo: varios microservicios o varios servicios web) de forma que unas necesiten información de otras, no es aconsejable que se llamen directamente. Es decir, no se recomienda que en el código de la API 1 se llame a la URL de la API 2. En caso de que se produzcan cambios, tendrías que revisar el código de todos los servicios para realizar las modificaciones.

Existen 2 técnicas que permiten gestionar mejor las llamadas entre API:

  • API Gateway: consiste en crear una API cuyo cometido es la centralizar todas las llamadas a cualquier API. De esta forma, cada vez que una API necesita invocar a otra, se lo solicita al Gateway y este se encarga de solicitárselo a su vez a quien corresponda.
    • Ventajas: permite centralizar también el balanceo de carga, la autenticación, la autorización, la monitorización, el enrutado y diversas políticas de empresa en un único lugar.
    • Desventajas: puede provocar cuellos de botella.
  • API Discovery: consiste en crear una API cuyo cometido sera la de ser un directorio con todas las API. De esta forma, cuando una API necesite invocar a otra, le pregunta al Discovery cuál es la URL a la que debe llamar. Si hay que cambiar la URL de una API, se hace solo en el Discovery. Cada servicio, cuando arranca, debe registrarse en el Discovery. También debe desregistrarse antes de detenerse. El Discovery debe encargarse de saber si cada API sigue activa, pudiendo desregistrar API que no contesten.
    • Ventajas: permite el registro y desregistro dinámico de API, las URL de las API pueden cambiar sin tener que detener ningún servicio en producción y admite balanceo de carga.
    • Desventajas: no soporta autenticación, autorización, monitorización o políticas.

Más Información

¿Y Tú, Qué Opinas?

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *