Consumir APIs REST / GraphQL

El consumo de información de las APIs (habitualmente REST aunque hay opciones como GraphQL) es una de las partes más importantes de cualquier aplicación Frontend.

Los conceptos sobre esta manera de trabajar son comunes para todas las tecnologías donde trabajamos con aplicaciones Frontend.

Por lo tanto, si entendemos bien los conceptos en una tecnología, no vamos a tener muchos problemas en aplicarlo en otros frameworks, librerías,…, sea en Qwik, Javascript puro o en tecnologías como Angular, React,…

Cuando trabajamos con la acción de consumir información de APIs externas estaremos obteniendo información de los recursos que nos proporciona una web para poder realizar las acciones como leer, crear, modificar y eliminar, lo que solemos conocer como CRUD.

No todas las APIs nos dan opción de modificar la información, muchas de ellas podríamos decir que son básicas, en las que reciben los datos de consulta devolviendo un resultado acorde a esos datos enviados. Esto es lo básico de una API y sin entender esto, no podremos avanzar.

El objetivo principal es que aprendamos a hacer uso de este recurso como es consumir una API en Qwik y por esa razón, os voy a proporcionar todas las claves para entender como podemos consumir información de APIs externas mediante casos prácticos.

Contenido del capítulo

Lo que aprenderemos en este capítulo se engloba en estos puntos importantes.

Repositorio de APIs públicas disponibles de manera gratuita

Seguramente haya referencias donde encontremos muchas más APIs (si las tenéis compartirlas por favor), pero estas referencias son super completas y se van actualizando periódicamente.

Las voy a clasificar en dos puntos principales:

REST

En cada una de estas referencias, podremos ver que las APIs se clasifican en categorías como Animales, Anime, Conversores de divisas,… Os invito a que veáis la referencia con calma para ver que opciones encontráis, seguramente haya algo de un tema que os interese verdaderamente y siendo así, os invito a que practiquéis con ello ajeno a lo que vamos a trabajar en este apartado, que puede que el tema usado no os apasione.

GraphQL

En cada una de estas referencias, podremos ver que las APIs se clasifican en categorías como APIs oficiales y APIs de solo demostración, entre otras opciones.

Como os he mencionado anteriormente, visualizar el documento con calma para ver si encontráis algo que os resulte interesante.

Una vez disponibles estos recursos, vamos a comenzar a trabajar consumiendo una API REST desde el navegador.

Preparativos API que vamos a usar en el capítulo

En este apartado realizamos los preparativos necesarios para poner en marcha la API que usaremos como fuente de datos, para consumirlo mediante REST y GraphQL.

La API que usaremos es la siguiente:

https://shorten-up.vercel.app/aSmT1qtBjC

Dentro de esta API tenemos las instrucciones para arrancar el proyecto de la API que consiste en estos 3 pasos sencillos, hasta tener la API en marcha:

  1. Clonar el repositorio donde queramos:
git clone https://github.com/qwik-book/qwik-book-api.git
  1. Seleccionar la versión de Node, que en este caso se usar la 20 e instalar dependencias
nvm use && npm install
  1. Arrancar proyecto
npm start

Una vez que arranquemos el proyecto, se nos habilita la URL principal con las instrucciones para acceder a los dos endpoints de la API REST y el endpoint correspondiente a la API GraphQL:

Llegados a este punto, ya tenemos la API que usaremos lista para consumir.

Pasamos al siguiente apartado donde veremos como consumir la API desde el navegador.

Consumiendo API REST desde el navegador

La API que acabamos de arrancar nos va a proporcionar información sobre los datos al detalle que corresponden a cada uno de los personajes. Tenemos las siguientes opciones:

Para acceder a esta API lo hacemos mediante este enlace:

http://localhost:3000

A tener en cuenta en este apartado

Si tenéis conocimientos de como se consume una API REST os animo a pasar al siguiente punto donde ya se puede ver como consumir las APIs REST desde Qwik

Esta será el apartado de la documentación:

Donde tenemos dos endpoints correspondientes a este tipo de API:

Lista de personajes

Para ver la lista de personajes, lo que tenemos que hacer es introducir la siguiente URL:

http://localhost:3000/characters

Reflejándose en el acceso a los datos dentro de nuestro proyecto, que lo aplicaremos en el siguiente paso:

const dataCharacters = await fetch(`http://localhost:3000/characters`).json();

Esto sería correspondiente a la lista de todos los personajes.

Información de un personaje seleccionado

Si hacemos uso de las peticiones con un personaje concreto, usaremos la siguiente estructura para obtener esa información:

http://localhost:3000/character/<ID> 

Y los ids existentes, los tenemos desde el 1 hasta el 21 (incluido). Si seleccionamos 22 o algún valor que no esté en ese rango, nos dará un mensaje de aviso, de que no existe ese personaje seleccionado.

  • Existe: http://localhost:3000/character/2
  • No existe

Consumir API REST en Qwik

Antes de empezar a trabajar

Asumo que ya tenéis el proyecto creado y disponible para trabajar con ello. El nombre asignado el proyecto (recomendado) 11-apis-rest.

Comenzamos con este apartado, donde vamos a obtener la lista de personajes de Breaking Bad con la siguiente URL:

http://localhost:3000/characters

Creamos primero la función para consumir los datos de la URL que acabamos de ver y esto lo hacemos dentro de la carpeta api (la creamos dentro de src) en el fichero characters.ts añadiendo el siguiente código

// 0 - Fichero alojado en: src/api/characters.ts

// 1.- URL principal de la API - BASE API URL
const API_URL = 'http://localhost:3000';

// 2.- Reniendo en cuenta la URL http://localhost:3000/characters
export const charactersAPI = async (
    controller?: AbortController
): Promise<Array<any>> => {

    // 3.- CREAMOS el endpoint
    const endPoint = `/characters`;

    // 4.- Formamos la URL completa del recurso para la lista de personajes
    const url = `${API_URL}${endPoint}`;

    console.log('Vamos a obtener datos desde =====>', url);

    // 5.- Pedimos los datos
    const data = await fetch(url, {
        method: 'GET',
        signal: controller?.signal, 
        // 8.- Uso de Abort Controller (controller.signal). 
        // Gestionamos las posibles cancelaciones en ejecución
    });
    console.log('FETCH resolved');

    // 6.- Lista de personajes
    const json: Array<any> = (await data.json());

    // 7.- Devolvemos resultado
    return Array.isArray(json) ? json : Promise.reject(json);
};

useResource$ - Estado computacional

En el capítulo anterior hemos trabajado con la gestión de estado, que son los encargados de gestionar valores de estado común y también hemos trabajado con un valor de estado computacional, el useComputed$ que observa y devuelve inmediatamente los datos, con opción a modificarlo.

En este caso, trabajaremos con useResource$(), que tendrá un funcionamiento parecido a useComputed$ pero este hook se usará específicamente para observar los cambios de estado a la hora de obtener información remota, como es una API.

Qwik - useResource$()

Si queréis profundizar con más información acerca de este hook, siempre podéis acceder al enlace de la documentación que os proporciono a continuación:
https://shorten-up.vercel.app/KKXL0zR6Ex

Al tener disponible la función para obtener los datos REST de la URL seleccionada, vamos al componente correspondiente a la ruta inicial (src/routes/index.tsx) y añadimos la función useResource$() de la siguiente manera:

// 1.- Importar 
import { component$, useResource$ } from '@builder.io/qwik';

export default component$(() => {
    
    // 2 Para cargar el contenido de la función que nos trae la información de la API
    const charactersListResponse = useResource$<any>(() => {

    });
    return <>
        <h1>Lista de Personajes principales de Breaking Bad</h1>
    </>
});

El resultado actual es el siguiente:

Analizándolo con más detalle, ¿En qué consiste la función useResource$()?

Este método nos va a permitir obtener los valores de una función de forma asincrónica.

Se llamará a la función asíncrona pasada como su primer argumento cuando se monte el componente y cuando cambien los valores rastreados.

Ahora vamos a trabajar dentro de useResource$(), aplicando la llamada a la API obteniendo los datos necesarios con el año actual:

// 1.- Imports necesarios
import { component$, useResource$, useStore} from '@builder.io/qwik';
import { charactersAPI } from '~/api/characters';

export default component$(() => {

    // 2
   // 2 Para cargar el contenido de la función que nos trae la información de la API
  const charactersListResponse = useResource$<any>(({ cleanup }) => {
    // 3.- Es buena práctica usar `AbortController` para abortar (cancelar) la obtención de datos si
    // llega una nueva solicitud. Creamos un nuevo `AbortController` y registramos una `limpieza` (cleanup)
    // función que se llama cuando se vuelve a ejecutar esta función.
    const controller = new AbortController();
    cleanup(() => controller.abort());
    // 4.- Llamar a la función y obtener el resultado
    return charactersAPI(controller);
  });
    return (
    <>
        <h1>Lista de Personajes principales de Breaking Bad</h1>
        <p>AQUÍ DEBEMOS DE PINTAR LA LISTA DE PERSONAJES</p>
    </>
    );
});

Y esto es lo que tenemos ahora:

El valor charactersListResponse que es el valor que se asigna desde useResource$() y tiene estos tres posibles resultados:

  • pending: los datos aún no están disponibles, es decir, mientras está haciendo el proceso de obtener los datos, en estado de carga.

  • resolved: los datos están disponibles.

  • reject: los datos no están disponibles debido a un error o tiempo de espera superado.

Ahora ya después de usar la función useResource$() y conociendo los posibles estados de las respuesta, debemos de incrustar el componente <Resource /> que será lo que obtenemos en la vista.

Qwik - Resource

Lo que vamos a aplicar en este componente que tiene relación directa con el hook useResource$() lo hacemos basándonos en este punto de la documentación:
https://shorten-up.vercel.app/UnGxxlF27X

Lo que vamos a añadir es la siguiente estructura:

<Resource
    value={ 
    /* VALOR ASIGNADO a la constante desde useResource$
    /* En este caso: charactersListResponse*/
    }
    onPending={() => <div>Cargando...</div>} // Mientras está ejecutando la carga
    onRejected={() => 
        <div>Fallo a la hora de cargar la lista de personajes de Breaking Bad</div>} // Rechazado
    onResolved={(result) => { 
        // Resuelto, donde renderizamos los resultados
        return <div><CONTENIDO></div>;
    }}
/>

Aplicándolo a nuestro proyecto:

// Añadir "Resource" en el import
import { component$, useResource$, useStore, Resource } from '@builder.io/qwik';
...

export default component$(() => {
    ....
    return (
    <>
        <h1>Lista de Personajes principales de Breaking Bad</h1>
        <Resource
            value={charactersListResponse}
            onPending={() => <div>Cargando...</div>} // Mientras está ejecutando la carga
            onRejected={() => (
            <div>Fallo a la hora de cargar la lista de personajes</div>
            )} // Rechazado
            onResolved={(result) => {
            return <div>{JSON.stringify(result)}</div>; 
            }}
        />
      
    </>
    );
});

Quedando de la siguiente forma, como se puede apreciar ya coge todos los datos de las carreras y lo incrusta como una cadena de texto (que por cierto, queda feo, pero podemos comprobar de una manera rápida que obtiene bien la información):

Lo que tenemos que hacer es ponerlo más organizado y más vistoso y esto lo haremos en formato lista:

<Resource
    value={charactersListResponse}
    onPending={() => <div>Cargando...</div>} // Mientras está ejecutando la carga
    onRejected={() => (
        <div>Fallo a la hora de cargar la lista de carreras</div>
    )} // Rechazado
    onResolved={(result) => {
        return result.length ? (
        <ul class="list">
            {result.map((character: any, index: number) => (
            <li key={String(character.id).concat(character.name.toLowerCase())}>
                <a href={character.url} target="_blank">
                {character.name}
                </a>{' - '}
                ({character.description}) - <Link href={`/details/${index + 1}`}>Más detalles</Link>
            </li>
            ))}
        </ul>
        ) : (
        <p>
            Sin resultados - Comprueba que la API está en funcionamiento
        </p>
        );
    }}
/>
      

El resultado será el siguiente:

Vamos a darle un mínimo de estilo, para que se pueda visualizar bien el formato lista y muestre el contenido de una manera más limpia en src/global.css:

ul {
  margin: 0% 5%;
}

h1 {
  margin: 1rem 0rem 1rem 2rem;
}

.list li::before {
  /*https://cloford.com/resources/charcodes/utf-8_misc-symbols.htm coger el valor HEX para content*/
  content: "\26B9";
  color: rgb(10, 95, 34);
  display: inline-block;
  width: 1em;
  font-weight: 800;
  margin-right: 1em;
}

ul li {
  list-style: none;
}

Quedando de la siguiente forma:

Si por lo que sea la API no responde, por ejemplo la API no está operativa por estar parada, debe de mostrarnos el siguiente estado:

Obtener los datos del personaje seleccionado por ID

Una vez que hemos realizado el primer apartado, donde recibimos la información de todos los personajes principales de Breaking Bad, llegará el momento que nos interesa filtrar estos datos, para obtener únicamente los correspondientes al seleccionado.

Para poder trabajar con lo siguiente, debemos de recordar lo aprendido en el apartado Obtención del parámetro ruta desde una URL del capítulo Routing.

Teniendo esto claro, vamos a src/routes y dentro del directorio creamos un directorio details y dentro de este otro llamado [id] que se encargará de obtener el parámetro de manera dinámica junto con su fichero index.tsx.

Quedará de la siguiente manera la estructura de directorios y ficheros:

src/
  └── routes/
        |── index.tsx
        └── details/
                └── [id] # details/1, details/2,...
                      └── index.tsx

Definimos la función que se encargará de llamar a la API, para obtener los datos del personaje seleccionado. Vamos al fichero creado anteriormente como src/api/characters.ts y añadimos lo siguiente con lo que actualmente existe:

export const getCharacterAPI = async (
    id: string,
    controller?: AbortController
): Promise<object> => {

    // 1.- CREAMOS el endpoint en base al id seleccionado
    const endPoint = `/character/${id}`;

    // 2.- Formamos la URL completa del recurso para la lista de carreras
    const url = `${API_URL}${endPoint}`;

    console.log('Vamos a obtener datos desde =====>', url);

    // 3.- Pedimos los datos del año seleccionado
    const data = await fetch(url, {
    method: 'GET',
    signal: controller?.signal, 
    // 6.- Uso de Abort Controller (controller.signal). 
    // Gestionamos las posibles cancelaciones en ejecución
    });
    console.log('FETCH resolved');

    // 4.- Información del personaje seleccionado
    const json: object = (await data.json());

    // 5.- Devolvemos resultado
    return ('id' in json) ? json : Promise.reject(json);
};

Ahora en src/routes/details/[id]/index.tsx añadimos lo siguiente:

import { Resource, component$, useResource$ } from '@builder.io/qwik';
import { useLocation, Link } from '@builder.io/qwik-city';
import { getCharacterAPI } from '~/api/characters';

export default component$(() => {
  const location = useLocation();
 
  const getCharacterResponse = useResource$<any>(({ track, cleanup }) => {
    track(() => location.params.id); 
    const controller = new AbortController();
    cleanup(() => controller.abort());
    return getCharacterAPI(location.params.id, controller);
  });
  return <>
    <h1>Personaje seleccionado: ID {location.params.id}</h1>
    <Resource
        value={getCharacterResponse}
        onPending={() => <div>Cargando...</div>} // Mientras está ejecutando la carga
        onRejected={() => (
          <div>Personaje no encontrado</div>
        )} // Rechazado
        onResolved={(result) => {
          return <div>
            <h2>{result.name}</h2>
            <p>{result.description}</p>
            <p>Número de episodios en los que participa: {result.episodes}</p>
            <p><a href={result.url}>Más información</a></p>
            <Link href={'/'}>Volver</Link>
          </div>
        }}
      />
  </>;
});

Lo que se añade como novedad respecto a la lista de personajes (/) es el uso de useLocation, hook que hemos usado anteriormente en el capítulo de Routing, por lo que no es algo nuevo.

Añadimos para observar los cambios con track del valor location.params.id para que pida a la API los datos cada vez que seleccionemos un nuevo ID del personaje.

Guardamos y ejecutamos diferentes opciones.

Si volvemos a la ruta principal (/) podemos ir accediendo a la información individual de cada personaje si hacemos click sobre Más detalles (1) sobre cada uno de los elementos existentes digiriéndose a la ruta especificada (2)

Una vez realizado click:

Llegados a este punto ya hemos trabajado con la forma de consumir APIs REST con los dos endpoints disponibles, usando el elemento Resource con el hook useResource$().

Ahora vamos a comenzar con el apartado de la API de GraphQL, desde el navegador mediante el uso del playground.

Resultado de lo trabajado en este apartado

Abriendo el proyecto 11-01-apis-rest podemos encontrar todo el código trabajado hasta ahora:

El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/t4RtWYewKR

Consumiendo API GraphQL desde el navegador

Después de haber completado la manera de consumir una API REST paso a paso, vamos a realizar el mismo proceso con las APIs GraphQL, que prácticamente es lo mismo pero a la hora de hacer la llamada usaremos SIEMPRE el verbo POST, ya que así está establecido en este estándar.

Antes de entrar en detalles, vamos a usar el endpoint /graphql de la API que estamos consumiento en estos momentos, donde podremos obtener la lista de los personajes en conjunto y los detalles de manera individual.

Información complementaria

Si no habéis trabajado nunca con GraphQL, os animo que si tenéis interés, toméis este curso GRATUITO impartido por mi en el que para obtener unos conocimientos básicos de como se trabaja en el Playground, lo haremos con la sección Trabajando en la Interfaz GrahQL Playground — Un GraphiQL mejorado:
https://shorten-up.vercel.app/1qQ2OmSPac

Una vez que accedamos, tenemos la siguiente apariencia (puede cambiar algo), el Playground donde vamos a realizar las diferentes consultas para obtener la información exacta que necesitemos.

Teniendo como referencia la documentación Docs (1) para expandir los puntos de dicha documentación:

Una vez realizado el click tenemos (2) se muestra la lista de elementos que pertenecen a nuestra API.

Estos elementos son los llamadas definiciones que serán como los endpoints, pero todo dentro de una consulta, pudiendo usar uno, dos y reusar el mismo con diferentes alias.

Creamos dos consultas, por un lado la lista de los personajes principales de Breaking Bad (characters) que es una consulta simple y por otro, una consulta un poco más compleja donde trabajamos con los Query Variables (Apartado Variables) para aplicar filtros y obtener los detalles de un personaje concreto mediante id (character).

Esto como se hace no lo voy a explicar, ya está explicado en el curso que os he adjuntado. Nos centramos en hacer la consulta y coger esa información para consumir desde Qwik.

Primera consulta — Lista de personajes

Consulta simple en el que usamos el resolver characters para obtener la lista de todos los personajes principales existentes, donde nos interesa obtener el id de cada uno de ellos para la segunda consulta.

Seleccionamos Query (1) para ver los resolvers disponibles, escribimos la consulta en el apartado de la mitad (2) y a la derecha su resultado (3).

Se ha especificado con lo mínimo posible para obtener el id que usaremos en la siguiente consulta junto con el nombre del personaje y votos recibidos (esto podemos prescindir si queremos).

Lo que tendríamos que tener en cuenta es lo siguiente:

  • Endpoint: http://localhost:3000/graphql

  • body: Dentro de la propiedad query enviamos el contenido de la consulta que sería lo que corresponde a la siguiente estructura.

{
  characters {
    id
    name
    votes
  }
}

Segunda consulta — Obtener la información del personaje seleccionado

Ahora que ya sabemos que podemos filtrar el personaje con el id que apliquemos (de 1 a 21 incluido) vamos a aplicar la siguiente consulta en el que ya estamos metiendo otro elemento, el uso de Query Variables (Variables), que son las variables que usaremos para poder hacer consultas dinámicas:

  • 1: Consulta donde ya hacemos uso del filtro añadiendo los argumentos con los Query Variables (apartado Variables, depende del playground aparece de una manera u otra). Se añadirá con este contenido de la consulta:
query getSelectCharacter($id: Int!) {
  character(id: $id) {
    name
    description
    episodes
    url
    votes
  }
}
  • 2: El filtro para obtener los datos mediante el uso de Query Variables. Y aquí, a continuación lo que corresponde al filtro que estamos aplicando:
{
  "id": 1
}
  • 3: Resultado aplicando correctamente el filtro para obtener solo los datos cuyo id es 1.

Llegados a este punto, vemos dos maneras de realizar diferentes tipos de consultas:

  • Sin uso de Query Variables
  • Utilizando valores para filtrar los resultados / seleccionar elementos con más detalle.

Es importante que sepáis trabajar con la documentación de las APIs, ahí está toda la clave. Si os desenvolvéis bien, podréis trabajar consumiendo cualquier API que os encontréis por el camino y en el caso de las APIs de GraphQL, las encontraréis documentadas, por lo menos con la estructura del schema.

Con esto, terminamos este apartado y usando estos ejemplos, vamos a consumirlo con Qwik, tal y como lo hemos hecho con los endpoints correspondientes a la API REST.

Consumiendo API GraphQL en Qwik

Para consumir una API de GraphQL siempre necesitamos hacer las llamadas de tipo POST. Lo que necesitamos mínimo es lo siguiente:

Teniendo en cuenta lo siguiente, montamos la función para poder realizar las operaciones con una API de GraphQL.

Crear un nuevo proyecto

Creamos un nuevo proyecto para tenerlo todo bien separado con el nombre asignado (recomendado) 11-02-apis-graphql.

Basándonos en lo visto con la API REST, usando su base, dejamos la función de la siguiente forma creando un nuevo fichero dentro del directorio api llamando al fichero characters.ts:

// 0 - Fichero alojado en: src/api/characters.ts

// 1.- Endpoint principal
const API_URL_GRAPHQL = 'http://localhost:3000/graphql';
// 2.- Función donde se realizan la consultas
export const breakingBapCharactersGraphQLAPI = async (
    body: { query: string; variables?: object }, // 3.- Consultas obligatorias + filtros
    controller?: AbortController
): Promise<Array<any>> => {
    // 4.- Realizamos la llamada fetch con POST pasándole siempre el body con mínimo la "query" (consulta)
    const resp = await fetch(API_URL_GRAPHQL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
        signal: controller?.signal,
    });
    console.log('FETCH resolved');
    // 5.- Extraemos la propiedad data, que siempre es el valor principal que tenemos en común
    const { data } = await resp.json();
    console.log(data);
    return data || Promise.reject(data);
};

Teniendo la función definida, lo que nos hace falta es consumirla desde la parte frontal, donde se visualizarán los datos.

Basándonos en los anterior, vamos a src/routes/index.ts y añadimos el contenido para obtener la lista de personajes:

1.- Importamos lo necesario 
import { Resource, component$, useResource$, useSignal } from '@builder.io/qwik';
import { breakingBapCharactersGraphQLAPI } from '~/api/characters';

export default component$(() => {
   const selectCharacter = useSignal(1);
  // 2.- Accedemos a la API
  const charactersListResource = useResource$<any>(({ cleanup }) => {
    const controller = new AbortController();
    cleanup(() => controller.abort());
    // Llamada a la función pasando únicamente la consulta en "query"
    return breakingBapCharactersGraphQLAPI(
      {
        query: `{
          characters {
            id
            name
            votes
          }
        }
        `,
      },
      controller,
    );
  });

    // 3.- Pintamos el resultado
    return (
        <>
        <h1>Personajes Breaking Bad</h1>
        <Resource
            value={charactersListResource}
            onPending={() => <div>Cargando...</div>}
            onRejected={() => <div>Error al cargar la lista de personajes</div>}
            onResolved={({ characters }) => {
            return characters.length ? (
                <ul class="list">
                    {characters.map((character: any) => (
                        <li key={character.id}>
                        ID: {character.id} / Name: {character.name}
                        </li>
                    ))}
                </ul>
                ) : (
                <p>Sin resultados</p>
            );
            }}
        />
        </>
    );
});

Estilos CSS

Al ser un nuevo proyecto, añadiremos los estilos que se han establecido antes en 11-01-apis-rest en global.css para darle una apariencia más agradable

Y su resultado será el siguiente:

Opciones de selección de personajes: detalles del personaje

Ahora ya tenemos los personajes a mano, solo nos queda generar dinámicamente los botones asociados a los personajes, para que cuando hagamos click, podamos cargar los detalles que corresponden a ese personaje, algo como lo que hemos realizado en el apartado anterior, consumiendo la API GraphQL en el navegador.

Cambiamos el código a lo siguiente:

1.- Importamos lo necesario 
import { Resource, component$, useResource$, useSignal} from '@builder.io/qwik';
import { breakingBapCharactersGraphQLAPI } from '~/api/characters';

export default component$(() => {
  // 2.- Seleccionamos 'Walter White' por defecto
  const selectCharacter = useSignal(1);
  // 3.- Accedemos a la API
  const charactersListResource = useResource$<any>(({ cleanup }) => {
    ...(Sin cambios)
  });

    // 4.- Pintamos el resultado
    return (
        <>
        <h1>Personajes Breaking Bad</h1>
        <p>Personaje seleccionado: {selectCharacter.value}</p>
        <Resource
            value={charactersListResource}
            onPending={() => <div>Cargando...</div>}
            onRejected={() => <div>Error al cargar la lista de personajes</div>}
           onResolved={({ characters }) => {
                return characters.length ? (
                <div>
                    {characters.map((character: any) => (
                    <button key={character.name.toLowerCase()}>{character.name}</button>
                    ))}
                </div>
                ) : (
                <p>Sin resultados</p>
                );
            }}
        />
        </>
    );
});

Y su resultado será el siguiente:

Añadimos la acción de click en los botones (dentro de <Resource />), para que actualice la información del continente seleccionado:

<div>
    {characters.map((character: any) => (
    <button key={character.name.toLowerCase()} onClick$={() => selectCharacter.value = character.id}>{character.name}</button>
    ))}
</div>

Por defecto, estará seleccionado el id 1 que corresponde a Walter White.

Si hacemos click en cualquiera de los otros personajes, irá cogiendo el id que le corresponde y con eso ya tendríamos lo necesario para poder cargar los detalles de cada selección.

  • Jesse Pinkman
  • Saul Goodman

Llegados a este punto ya tenemos el primer apartado realizado, lo que nos queda es añadir otro <Resource/> para mostrar los detalles del personaje seleccionado.

Añadimos el siguiente código para obtener la información basándonos en lo visto en la segunda consulta que hemos hecho antes que era lo siguiente (ahora está mostrando los datos de Jesse Pinkman):

Lo aplicamos al código, para que coja el valor del continente en base a nuestra selección:

import {
    component$,
    Resource,
    useResource$,
    useSignal,
} from '@builder.io/qwik';
import { breakingBapCharactersGraphQLAPI } from '~/api/characters';

export default component$(() => {
    ...
    const characterSelectResource = useResource$<any>(
    ({ track, cleanup }) => {
    // 1.- Observamos los cambios del continente seleccionado
    track(() => selectCharacter.value);
    
    const controller = new AbortController();
    cleanup(() => controller.abort());
    // 2.- Consulta para obtener los detalles del personaheje seleccionado

    return breakingBapCharactersGraphQLAPI(
        {
            query: `
              query getSelectCharacter($id: Int!) {
                character(id: $id) {
                  name
                  description
                  episodes
                  url
                  votes
                }
              }
            `,
            variables: { 
                id: selectCharacter.value
            }
        },
        controller
    );
    }
  );
    return (
        <>
    ...
        <hr />
        <Resource
            value={characterSelectResource}
            onPending={() => <div>Cargando...</div>}
            onRejected={() => <div>Falla al cargar los detalles del personaje seleccionado {selectCharacter.value}</div>}
            onResolved={() => <p>Pendiente para mostrar detalles del personaje seleccionado</p>}
        />
    </>
});

Y ahora vamos a probarlo. Al cargar hace lo siguiente (en la consola de ejecución de los eventos del servidor), siendo character los detalles correspondientes al id relacionado a Walter White:

Ahora si selecciono Jesse Pinkman, esto es lo que pasaría (en la consola del navegador).

Recordar sobre lo que ocurre al haber cambios

Ahora recordad, que con cualquier cambio se visualizará el log dentro de la consola del navegador. Este momento estará todo listo en el navegador y cualquier evento de cambio en los valores del estado hará que se ejecute en el navegador y no en el servidor como en la primera carga.

Probad con otros personajes para ver resultados, a ver si funciona con coherencia.

Aquí los diferentes casos haciendo diferentes opciones:

  • Saul Goodman
  • Gustavo Fring

(Probad todos vosotros si tenéis curiosidad, el funcionamiento es correcto)

Pintar los resultados de los detalles en formato lista

Lo más difícil está hecho, lo que falta es reflejar esos resultados en una forma de lista (o como queramos, tampoco nos vamos a parar mucho en ello).

Añadimos dentro del código JSX debajo de los botones de selección lo siguiente:

import {
    component$,
    Resource,
    useResource$,
    useSignal,
} from '@builder.io/qwik';
import { breakingBapCharactersGraphQLAPI } from '~/api/characters';

export default component$(() => {
    ...
    return (
    <>
        ...
        <hr/>
        <Resource
            value={characterSelectResource}
            onPending={() => <div>Cargando...</div>}
            onRejected={() => <div>Falla al cargar los detalles del personaje seleccionado {selectCharacter.value}</div>}
            onResolved={({character}) => {
            return (character ?
            <ul class="list">
                {Object.keys(character).map((key: any) => (
                <li key={key.toLowerCase()}>
                    {character[key]}
                </li>
                ))}
            </ul> : <p>Sin resultados</p>
            );
            }}
        />
    </>
    );
});

Y el resultado se refleja de la siguiente forma, en este caso de manera inicial:

Seleccionando Skyler White:

Resultado de lo trabajado en este apartado

Abriendo el proyecto 11-02-apis-graphql podemos encontrar todo el código trabajado hasta ahora:

El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/ZoDukiaOeF

¿Qué hemos aprendido en este capítulo?

Al finalizar este capítulo, deberíamos de ser capaces de:

Conclusión

Llegados a este punto hemos aprendido como consumir APIs REST y GraphQL con Qwik.

Este capítulo debemos de considerarlo muy importante, ya que a día de hoy prácticamente todas las aplicaciones hacen uso de la comunicación con APIs REST / GraphQL y entendiendo bien los conceptos de este capítulo, podremos realizarlo con cualquier API que deseemos usar.