Introducción a las APIs

Empecemos por conocer lo que es una API. Estas letras son el acrónimo de application programming interface, en español lo podemos traducir como Interfaz de programación de aplicaciones, su objetivo fundamental es permitir que la comunicación entre 2 o más elementos o sistemas.

Con la definición anterior, también indicar que existen diferentes tipos de API, una de las más conocidas es la API que ofrece Windows para poder acceder a los recursos del sistema operativo y construir aplicaciones sobre él. Otro tipo de API y es el foco de nuestra atención, es el tipo encargado del transporte de datos entre sistemas, siendo ampliamente utilizado actualmente.

Arquitecturas de API para transporte de datos

Dado su amplio uso, las APIs de transporte de datos se pueden organizar o implementar de diferentes formas, teniendo como aspectos diferenciales, el formato de datos y la forma como se definen las reglas de comunicación entre los principales puntos a definir, veamos algunos tipos de arquitectura de API para transporte de datos:

  1. REST (Representational State Transfer o Transferencia del estado representacional), es actualmente la arquitectura más usada, utiliza el protocolo http como base para el transporte de datos, cada petición u operación es independiente y realiza una tarea completa, es decir no hay dependencia entre operaciones.
  2. GraphQL, al igual que REST, esta basado en el protocolo http, pero se diferencia en la forma como realiza las operaciones, ya que estas se basan en un lenguaje de consulta y manipulación de datos, lo cual hace posible manejarlas de manera más detallada y óptima pero incrementando un poco la complejidad.
  3. Websocket. Es una arquitectura basada en el concepto de socket, el cual mediante el protocolo TCP, crea una conexión directa entre 2 puntos y la mantiene abierta, de forma tal que cualquier operación se ejecuta en el menor tiempo posible. Es indicada para casos en que la comunicación debe realizarse en tiempo real.
  4. Webhook. Es un tipo de arquitectura un poco diferente a las anteriores, el inicio del proceso se atribuye a eventos, es decir, es a partir de una acción, que el proceso se inicia, como por ejemplo, cuando un registro en el sistema está en la condición X, se ejecuta la petición a la URL y en general son respuestas a procesos, por lo que esta arquitectura se denomina API reversa o inversa.
  5. RPC y gRPC. Esta arquitectura actua como un proceso remoto ejecutado en ambiente local, trabaja bajo http, tcp, o udp. Se base en la definición de un servicio que es invocado por el cliente y resuelto por el servidor, a diferencia de REST, tanto como cliente como servidor comparten definiciones del servicio, es decir debe haber correspondencia en sus implementaciones, en REST cada lado implementa de su forma y es solo en el transporte de datos que debe correspondencia.
  6. SOAP. Mas que una arquitectura es protocolo de comunicación, basado en http pero con transporte de datos hecho en XML (eXtensible Markup Language, o en español 'Lenguaje de Marcado Extensible'), su forma de trabajo es basada en acceso a objetos y tiene un conjunto de reglas y restricciones que la hacen un poco más compleja de manejar pero posee mayor seguridad.
1698457900830
Tipos de arquitecturas más conocidos. Tomado de: https://www.linkedin.com/in/brijpandeyji/

Detallando una API REST

Como comentamos anteriormente, una API REST es una herramienta o mecanismo que permite interconectar componentes o sistemas. Su uso actualmente se ha enfocado en ser la forma ideal para implementar procesos de gestión de datos, conocidos como CRUD (C-reate, R-ead, U-pdate y D-elete), de uso común para cualquier aplicación.

El formato común de transporte de datos este tipo de APIs es JSON (Javascript Object Notation), el cual por ser liviano y muy bien estructurado permite que podamos representar de forma simple y entendible los modelos que serán procesados.

La comunicación entre el cliente y el servicio se realiza a través de protocolo http como indicamos anteriormente y cada tipo de operación está relacionada o identificada por un verbo, veamos que significa ello.

Para saber que operación se va a realizar existen 2 elementos fundamentales que permiten definirla, la ruta o endpoint y el verbo. Cuandon realizamos operaciones sobre una API REST, estas las denominamos peticiones, solicitudes o requisiciones, recordando que quien inicia la operación es el cliente, conversando con el servicio que dispone o habilita la API.

Ruta o endpoint de una petición en API REST

Es la dirección o url que se va a ejecutar cuando realicemos la petición, solicitud o requisición, de esta forma el protocolo http, identificará que servidor va a responder y que modelo o entidad es la que se va a procesar. Sigue a continuación lo que se denomina la anatomía de una ruta o endpoint.

image
Elementos o partes de un endpoint

Verbos de una API REST

Para que esta conversación sea posible, existen lo que se denominan verbos, que son los que definen que tipo de operación se va a realizar, los verbos más comunes en una API REST son:

  • GET: se define o identifica como el verbo que permite realizar consultas, es importante indicar que este verbo trabaja de la misma forma que en el protocolo http, donde los datos que enviamos quedan expuestos en la url. Esta operación es la R de R-ead.
  • POST: es el verbo que nos permite definir operaciones de creación de registros, los datos al igual que en el protocolo http, viajan por un mecanismo interno o cuerpo de la petición, no son visibles. En este caso enviamos los datos en lo que denominamos el cuerpo o body de la petición, generalmente se envian en formato JSON
  • PUT: mediante este verbo hacemos actualizaciones completas de registros, es decir, lo que enviamos va a sustituir lo que existe, generalmente en la url o endpoint de la petición se envía un identificador que permite saber sobre que registro se hace la actualización, importante tener claro este punto, ya que en REST tenemos 2 formas de actualizar registros, los datos al igual que en POST, no son visibles en el envío.
  • PATCH: con el uso de este verbo, podemos realizar actualizaciones de forma incremental, es decir podemos cambiar partes de registro sin afectarlo por completo, es decir son actualizaciones parciales. Los datos en este caso de igual forma viajan mediante el cuerpo o body de la petición, es decir no son visibles en el envío. En la ruta o endpoint generalmente se indica un parámetro que pemite identificar el registro a ser actualizado.
  • DELETE: a través de este verbo podemos indicar la operación de borrado de registros. No es necesario enviar body, ya que en la ruta o endpoint se indica un parámetro que permite identificar que registro se va a eliminar.

En las definiciones anteriores se indica que para identificar un registro, la buena práctica o común implementación es indicarlo en la ruta, pero no hay una restricción técnica que sea de obligatorio cumplimiento.

ReactJS y los hooks

Iniciamos recordando que ReactJS es una libreria que permite implementar y construir el front-end para aplicaciones web, esta libreria es de amplio uso, por estar basada en Javascript tiene una comunidad fuerte que apoya su evolución continua.

React trabaja de la forma en que la aplicación sólo comunica al navegador la página inicial, esto se denomina SPA o Single Page Application, y toda la lógica es controlada e implementada por la libreria, esto hace que el performance y la usabilidad de cara al usuario sea mucho mejor que las aplicaciones web tradicionales (basadas en html+css+JS y renderizado total).

Uno de los puntos fuertes que surgió a partir de la versión 16.8, es la implementación en base a componentes funcionales, lo cual facilitó de forma enorme la construcción e implementación, de igual manera en esa versión nacieron los hooks, pero que son y como funcionan?, en las siguientes lineas lo revisamos en detalle.

Entiendo los hooks de ReactJS

Empecemos por definir y entender que son los Hooks, expresémoslo en los términos más simples, un hook es una función, partiendo de esa premisa entonces podemos indicar que no es cualquier función, está claro ello, en ese caso podemos decir:

Un hook es una función especial con un objetivo especifico, simplificar algún proceso que hasta el momento se realizaba de otra forma.
0*H-KrMzFt_kMk2m_X

Con este concepto más amplio, podemos decir que los hooks, son funciones que simplifican procesos relacionados al ciclo de vida de componente funcionales, porque hasta la versión 16.7 de React, los componentes funcionales sólo recibían props y no era posible gestionar su estado, solo los componentes de clases (antigua forma de construir componentes, basada en POO ) soportaban está gestión.

Un detalle interesante, es que dada la usabilidad y potencia de los Hooks, no solo la librería base React actualmente tiene hooks, ahora otros paquetes de amplio uso también implementan hooks, tal como es el caso de react-router-dom por ejemplo.

Gestión de estado - useState en ReactJS

Uno de los puntos críticos y de suma importancia cuando construimos aplicaciones de una sóla página (SPA - Single Page Application) es la gestión de estado, dado que en el enfoque tradicional (html + css + js + server), la gestión es realizada mediante la gestión de sesiones en el lado del server, en el enfoque de ReactJS, la gestión es hecha a través de hooks, siendo el hook encargado de esta operación el useState.

Conociendo a useState

Este hook permite mantener el control de los estados de un componente, entiendo a los estados como las características o propiedades que definen el comportamiento y presentación del componente. Toda vez que un estado cambia, significa que el componente ha cambiado y por tanto debe actualizarse o renderizarse (es decir construirse nuevamente).

Tomemos un ejemplo práctico, una aplicación que presenta productos, en ese caso hablamos de un componente tipo "Galeria", que muestra datos y fotos de estos productos. Al iniciar no tiene productos, es decir, tenemos un estado tipo lista o arreglo, pero que está vacía al inicio, por lo tanto se presenta un mensaje de "Sin productos disponibles". Ahora mediante algún proceso (que vamos a entender más adelante), esa lista se llena con algunos datos, que debe suceder? que al recibirlos estos datos disparan un proceso de actualización del componente "Galeria".

Entendido el concepto teórico de funcionamiento de la gestión de estado, pasemos a colocar las manos en el código. Vamos a crear una aplicación con Vite y vamos a crear un componente funcional llamado Galeria. En este material puedes verificar como se realiza la creación de componentes en React.

Para definir un estado se sigue la siguiente anatomia:

//Definición de un estado
const [estado, setEstado] = useState(<valor inicial>);

Observa que siempre que se define un estado, se invoca al hook useState el cual nos retorna un arreglo que desestructuramos (recordar que la desestructuración está disponible en Javascript desde la versión ES2015) en 2 variables, una se refiere al estado en sí y otra es la función que permite asignar o atribuirle valor a ese estado.

Por ser de manejo especial, a los estados no le podemos asignar valor, como tradicionalmente lo hacemos en Javascript, entonces lo hacemos siempre a través de la función setteadora.

Por nuestra aplicación necesitar presentar múltiples productos, también va a ser necesario crear un componente para esta gestión. Es buena práctica en ReactJS, granular o despiezar de la manera más detallada la gestión de componentes, para que podamos aprovechar las características de la libreria y obtener un comportamiento óptimo de nuestras aplicaciones.

Veamos el código del  componente Galeria:

import React, { useState } from 'react'
import Product from '../Product/Product';

const productos = [
    {
        "_id": "652803510a598cf5573918f3",
        "nombre": "The Complete Common Core: State Standards Kit, Grade 5",
        "sku": "D82015FFBF",
        "precio": 12.88,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b006",
        "url": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg"
        },
        ]
    },
    {
        "_id": "652803510a598cf5573918f4",
        "nombre": "Flash Furniture 25''W x 45''L Trapezoid Red HP Laminate Activity Table - Height Adjustable Short Legs",
        "sku": "39F1B8A212",
        "precio": 117.26,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b00b",
        "url": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg"
        },
        ]
    },
    {
        "_id": "652803510a598cf5573918f3",
        "nombre": "The Complete Common Core: State Standards Kit, Grade 5",
        "sku": "D82015FFBF",
        "precio": 12.88,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b006",
        "url": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg"
        },
        ]
    },
    {
        "_id": "652803510a598cf5573918f4",
        "nombre": "Flash Furniture 25''W x 45''L Trapezoid Red HP Laminate Activity Table - Height Adjustable Short Legs",
        "sku": "39F1B8A212",
        "precio": 117.26,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b00b",
        "url": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg"
        },
        ]
    },
    {
        "_id": "652803510a598cf5573918f3",
        "nombre": "The Complete Common Core: State Standards Kit, Grade 5",
        "sku": "D82015FFBF",
        "precio": 12.88,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b006",
        "url": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/41gxkFaSFfL.jpg"
        },
        ]
    },
    {
        "_id": "652803510a598cf5573918f4",
        "nombre": "Flash Furniture 25''W x 45''L Trapezoid Red HP Laminate Activity Table - Height Adjustable Short Legs",
        "sku": "39F1B8A212",
        "precio": 117.26,
        "descripcion": "...",
        "imagenes": [
        {
        "_id": "654f7cae84c15e3104b1b00b",
        "url": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg",
        "nombre": "https://images-na.ssl-images-amazon.com/images/I/31WxfYlS8XL.jpg"
        },
        ]
    },
];


const Gallery = () => {
    //Definición del estado
    const [listaProductos, setListaProductos] = useState([]);

    const llenarProductos = () => {
        setListaProductos([...productos]);
    }

    //Si no hay producto emitimos un mensaje
    if (listaProductos.length === 0) {
        return <>
        <h1>No hay productos disponibles</h1>
        <button onClick={llenarProductos}>Cargar productos</button>
        </>
    }
    
    //Mostramos los productos
  return (
    <div style={{display:'flex', width:'100%', flexWrap:'wrap'}}>
    {
        listaProductos.map((p) => {
            return <Product product={p}></Product>
        })
    }
    </div>
    
  )
}

export default Gallery

Expliquemos un poco el código anterior, empecemos por definir el estado, esto se encuentra en la siguiente línea:

	//Definición del estado
    const [listaProductos, setListaProductos] = useState([]);

Como indicamos anteriormente, realizamos la llamada al hook useState el cual nos retorna 2 variables, nuestro estado listaProductos y su función setteadora setListaProductos, notese que le pasamos a useState un arreglo vacío como valor inicial, de esta forma estamos indicando que el estado va a ser de tipo arreglo.

Luego tenemos una expresión de función que nos va a servir para llenar el arreglo a partir de un evento, este evento será el click de un botón que hemos agregado al mostrar el mensaje de "No hay productos disponibles".

Observa que en esta función llenarProductos se realiza la asignación del estado, de un modo que para quien viene de Javascript puro o vanilla, puede en principio no ser entendida, vamos explicar un poco porque se hace así:

const llenarProductos = () => {
	setListaProductos([...productos]);
}

Observa que se usa el spread operator, el cual es una característica disponible de ES2015, este operador básicamente lo que hace es crear un nuevo arreglo y lo que pasamos como: ...<variable>, se copia inmediamente a este nuevo arreglo, de esta forma lo que estamos realizando es la creación de un arreglo con los datos de productos, al ser un nuevo arreglo hay cambio de estado y ReactJS reacciona, actualizando el componente. Y por qué no usar el método push por ejemplo, porque en ese caso estariamos agregando un valor, solo que el arreglo para React no estaria modificado, por ser un tipo de dato estructurado y manejado por referencia, en esencia por ser inmutable, en este caso, para React el arreglo sigue igual, no hay cambio de estado por lo tanto no se renderiza.

Avanzando en el código nos encontramos la implementación del componente, donde lo primero que realizamos es la verificación de existencia o no de productos, si no existen se muestra un mensaje y un botón para cargar productos. En caso de existir productos, se hace un recorrido, recordando que por ser un arreglo, tenemos disponible sus métodos nativos de Javascript, entre ellos el método map.

Recorremos el arreglo y para cada producto construimos un componente de tipo Product. Este componente recibe los datos de cada producto y hace una presentación en pantalla de las informaciones. Sigue a continuación el código del componente Product

import React from 'react'

const Product = ({product}) => {
  return (
    <div style={{display:'flex', width:'30%', border: '1px solid white', justifyContent:'center', flexDirection:'column', margin: '1rem',}}>
        <img src={product.imagenes[0]['url'] ?? './sinimagen.jpg'} style={{width:'100%', height:'20vh', objectFit:'contain'}}></img>
        <hr></hr>
        <div style={{height: '30vh'}}>
            <div>
                <h3 style={{overflowX:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', maxHeight:'100%', }}>{product.nombre}</h3>
            </div>
            <div style={{height: '20vh'}}>
                <p style={{overflow:'hidden', maxHeight:'15vh', textOverflow:'ellipsis'}}>{product.descripcion}</p>
            </div>
        </div>
        <div style={{display:'flex'}}>
            <div>
                Precio: {product.precio}
            </div>
            <div>
                SKU: {product.sku}
            </div>
        </div>        
    </div>
  )
}

export default Product

Este código es simple, lo que retorna es el HTML necesario para presentar los datos de productos, le hemos hecho algunos ajustes en cuanto a CSS para presentar la información de una forma agradable. Nota que valimos si hay imágenes en el producto, de lo contrario presentamos una imagen por defecto.

Ya hemos visto como funciona la gestión de estado, solo que en este caso nuestros datos están en memoria o en el código, luego hemos tenido que hacer un clic para poder cargarlos. Avancemos en el uso de otro hook que nos va a permitir, consultar una API externa y luego cargar mediante la actualización del estado.

Efectos secundarios - useEffect en ReactJS

Los efectos secundarios, son acciones que podemos ejecutar durante el ciclo de vida de un componente ReactJS, para ello detallemos  cual es el ciclo de vida que ReactJS implementa:

  • Montaje, es cuando el componente es creado.
  • Actualización, proceso ejecutado toda vez que un estado del componente cambia, por tanto es necesario reconstruir el componente
  • Desmontaje, cuando se destruye el componente, por ejemplo si nos cambiamos de vista.

Analizando este ciclo, entendemos que nuestro componente es creado, solo se actualiza por un cambio de estado, el cual es generado a partir de un evento, generalmente ejecutado o invocado por el usuario y luego se destruye. Pensemos ahora que deseamos presentar nuestros productos al momento de que se cargue el componente, sólo que estos productos seran consultados a una API externa, por lo cual no se van a mostrar de inmediato, en el mejor de los casos puede que sean algunos segundos de tiempo, pero puede pasar que sea necesario un tiempo mayor, o peor aún que se genere algún error, en ese caso nuestro usuario va a estar esperando con una pantalla blanca, esto no es correcto.

Es allí en ese punto anterior, que useEffect y los efectos secundario entran en escena. Ahora lo anterior con useEffect, queda de la siguiente forma:

  • Creamos el componente, sabiendo que no tiene datos, por lo tanto podemos informar al usuario de esto, o mejor aún podemos decirle que estamos buscando los datos.
  • Inmediatamente luego de creado el componente, podemos invocar a useEffect, el cual ejecutará un callback, donde nosotros tenemos la libertad de poder consumir la API indicada (recordando que esto se hace con promesas o async/await de Javascript).
  • Al llegar los datos podemos actualizar nuestro estado listaProductos, luego esto va a disparar la actualización del componente. Nuestro usuario va a estar atento y siempre informado de lo que sucede (usabilidad)

Y como se implementan los pasos anteriormente descritos?, vayamos al código, tomando en cuenta que vamos a consumir una API que se encuentra en la siguiente URL: https://apiexpress-x7sl.onrender.com/productos, esta API fue desarrollada por mi y es pública, así que puedes usarla para que realices tus ejercicios.

Implementando el consumo de una API con ReactJS

En nuestro caso, el consumo de datos que queremos realizar es a nivel del componente Galeria, el cual actualmente tiene los datos en memoria, como debemos ajustar su código, primero empecemos por conocer la anatomía del useEffect:

useEffect(() => {
      //Callback a ejecutar en el proceso de montaje y/o actualización
    
      return () => {
        //Callback a ejecutar en el proceso de desmontaje
      }
    }, [Arreglo de dependencias para saber cuando ejecutar el useEffect])

Con lo anterior podemos percibir que el useEffect cumple a cabalidad el ciclo de vida de componentes, de acuerdo a lo indicado anteriormente:

  • Ejecuta un callback (bloque de código o función) al hacer el montaje (Paso 1 del ciclo de vida) y al hacer actualizaciones (Paso 2 del ciclo de vida, esta ejecución es opcional y va a depender del arreglo de dependencias).
  • Puede ejecutar un callback al hacer el desmontaje (Paso 3 del ciclo de vida, este es opcional)

Sólo hay una restricción en el useEffect, se ejecuta siempre en el proceso de montaje de acuerdo a lo anterior.

Expliquemos ahora el arreglo de dependencias:

  • Si el arreglo no se indica, el useEffect se ejecuta tanto en el montaje como en cualquier cambio de estado.
  • Si el arreglo se indica vacio [], el useEffectse ejecuta solo en el proceso de montaje.
  • Si dentro del arreglo, indicamos algunos estados, el useEffect se ejecuta tanto en el montaje, como cuando esos estados indicados cambien.

Con ello tenemos flexibilidad para indicar, como queremos que sea ejecutado este efecto secundario.

Veamos como queda nuestro código luego de implementado el useEffect:

import React, { useState, useEffect } from 'react'
import Product from '../Product/Product';



const Gallery = () => {
    //Definición del estado
    const [listaProductos, setListaProductos] = useState([]);

    useEffect(() => {
      const obtenerDatos = async () => {
        const res = await fetch('https://apiexpress-x7sl.onrender.com/productos');
        const data = await res.json();
        setListaProductos([...data]);
      }

      obtenerDatos();

    }, []);
    

    //Si no hay producto emitimos un mensaje
    if (listaProductos.length === 0) {
        return <>
        <h1>Cargando productos</h1>
        </>
    }
    
    //Mostramos los productos
  return (
    <div style={{display:'flex', width:'100%', flexWrap:'wrap'}}>
    {
        listaProductos.map((p, i) => {
            return <Product key={i} product={p}></Product>
        })
    }
    </div>
    
  )
}

export default Gallery

Expliquemos los cambios que hemos realizado:

  • Ya no es necesario tener el arreglo de productos, ni el botón para cargar datos, ni el handler para el clic llenarProductos, con esto nuestro código ha quedado más simple.
  • Luego hemos implementado el useEffect de la siguiente forma:
useEffect(() => {
      const obtenerDatos = async () => {
        const res = await fetch('https://apiexpress-x7sl.onrender.com/productos');
        const data = await res.json();
        setListaProductos([...data]);
      }

      obtenerDatos();

    }, []);

Tenemos una función de callBack llamada obtenerDatos, la cual de forma asincronica, se conecta con la API (https://apiexpress-x7sl.onrender.com/productos) y obtiene una respuesta, luego pedimos solo los datos en formato JSON, tal como indicamos que la API los retornaba, un ejemplo de los datos se puede ver en la siguiente imagen:

image-1
Ejemplo de retorno de la API que estamos usando

Estos datos nos han quedado disponibles en la variable: data, ahora esta variable que es un arreglo de objetos, se lo asignamos al estado listadoProductos, con lo que explicamos anteriormente, este cambio de estado dispara la actualización del componente Galeria, y con ello hemos podido lograr el objetivo planteado, consumir datos via API y usando useState y useEffect.

En el siguiente video podrás apreciar la construcción de todo lo explicado anteriormente.