Développement web

Utiliser plusieurs API GraphQL avec Urql

June 2, 2021 — minutes de lecture
vues

Si vous êtes familier avec GraphQL, vous avec sûrement déjà eu besoin de récupérer des données sur des services tiers en parallèle de votre API GraphQL principale. Dans cet article, je résume comment j'ai réussi à utiliser plusieurs API GraphQL via un seul et unique client Urql.

Introduction

Récemment, j’ai dû migrer ma stack sous Apollo Client vers une autre librairie GraphQL. En cause, des problématiques de performances en partie dues à la façon dont la libraire parcourt l'arbre et les requêtes, provoquant des baisses de score Google lighthouse et empêchant la bonne indexation des pages sur la SERP Google. La librairie est lourde, et possède trop d’outils que je n’utilise pas.

En remplacement, j’ai opté pour Urql. Basée sur des observables, et plutôt légère, c'est une librairie avec une vision à l'opposé de ce que propose Apollo : à la place d’un outil « tout en un » pour répondre à toutes les problématiques, je compose moi-même mon « menu » avec des modules supplémentaires que j'ajoute en fonction de mes besoins. Cette approche me laissant le soin de contrôler finement ce qui est ajouté dans mon bundle front-end.

Mais voilà, une des fonctionnalité d’Apollo Client que j’apprécie beaucoup, c’est la composition des

Link
, me permettant, en autre, de créer plusieurs
HttpLink
pour plusieurs sources de données. Si vous êtes habitué à utiliser GraphQL, vous avez peut-être déjà dû vouloir récupérer de la donnée en parallèle de votre API GraphQL principale (d'un service tiers par exemple). Malheureusement, je n'ai pas trouvé d'information en ligne expliquant comment faire la même chose en passant par Urql. La plupart des réponses se résumant à dire « pourquoi utiliser plusieurs sources, ce n'est pas dans l’esprit GraphQL » (ce qui est vrai, en soit, mais je n'ai pas toujours le temps créer un serveur GraphQL pour fédérer deux sources de données).

Donc, si vous souhaitez savoir si il est possible de gérer plusieurs sources de données avec Urql et comment le faire, ne cherchez plus vous avez trouvé l'article qu'il vous faut.

Pré-requis : installation des modules nécessaires

Urql est un client GraphQL qui repose sur une collection de modules qui se connectent entre eux. Comme j’ai pu vous le présenter en introduction, c’est ce qui nous permet de composer la liste exhaustive des outils dont on va se servir. Voyez ces modules comme des opérateurs dans le monde des observables. Ça tombe bien, c’est exactement ce qui fait le fonctionnement de la librairie.

Ces modules, appelés des

exchange
, apportent les briques qui modifient les requêtes aux serveurs GraphQL.

L’ordre est important et doit être logique en fonction des transformations que vous souhaitez appliquer. D'après la documentation officielle, il faut toujours commencer par les tâches synchrones pour finir par celles asynchrones.

Lorsque vous configurez un client Urql, vous avez accès, par défaut, à certains exchange :

  • dedupExchange
    : assure que des requêtes identiques ne sont pas exécuté en double
  • cacheExchange
    : met le résultat des requêtes en cache
  • fetchExchange
    : se charge d'exécuter la requête HTTP (en utilisant l'API
    fetch
    )

Pour notre problématique, nous aurons besoin d’un

exchange
qui n'est pas présent dans le core Urql :
authExchange
.

De base, il permet de configurer facilement un système d'authentification (par exemple via JWT), mais il va surtout nous servir à modifier le contexte de notre requête à la volée.

Commençons par l’installer :

# Avec Yarn
$ yarn add @urql/exchange-auth

# Avec NPM
$ npm install @urql/exchange-auth
Bash

Et importons le :

import { authExchange } from '@urql/exchange-auth'
TypeScript

Création du client GraphQL

Cette fonction devra retourner la méthode de création du client Urql avec les bons paramètres de configuration et les différents

exchange
que vous souhaitez implémenter :

import { Client, createClient } from 'urql'

export function createGraphqlClient(): Client {
return createClient({
url: 'your-api-url', // Le client que vous souhaitez utiliser "par défaut"
fetchOptions: {
credentials: 'same-origin',
method: 'GET',
headers: {
Authorization: `Bearer ...`,
'Content-Type': 'application/json',
}
},
preferGetMethod: true,
exchanges: [],
})
}
TypeScript

Il nous suffit ensuite d’ajouter les

exchange
Urql de "base" (en savoir plus) :

import { ..., dedupExchange, cacheExchange, fetchExchange } from 'urql'

export function createGraphqlClient(): Client {
return createClient({
//...
exchanges: [
dedupExchange,
cacheExchange,
fetchExchange
],
})
}
TypeScript

Ajout de l'exchange d'authentification

Une fois le client configuré avec les options de base, on peut s'attaquer à l'ajout de

authExchange
.

L’idée globale est de calquer la méthode qu’on utiliserait avec Apollo Client (et les

Link
) pour gérer plusieurs sources de données. (Si vous souhaitez en savoir plus sur cette méthode, personnelement je me sers de l’article de Jamal Mashal que vous trouverez ici).

Pour résumer, on va ajouter dans le contexte de chaque requête (via le client ou via une intégration comme les hooks React) une clé

clientName
, optionnelle, qui, servira de condition pour la modification du reste du contexte (URL de la requête et potentiellement des headers si besoin d'un token d'authentification).

L'exchange

authExchange
prend en paramètre un objet qui nécessite 2 clés au minimum :
addAuthToOperation
et
getAuth
.

Pour

addAuthToOperation
:

addAuthToOperation({ operation }) {
// si la clé clientName n'existe pas dans le contexte, on retourne l'opération courante
if (!operation.context.clientName) {
return operation
}
const { clientName, fetchOptions } = operation.context
const options = typeof fetchOptions === 'function' ? fetchOptions() : fetchOptions ?? {}
switch (clientName) {
case 'endpoint-a': {
const headers = {
...options.headers,
Authorization: `Bearer ...`,
}
const context = {
...operation.context,
url: 'endpoint-a-url',
fetchOptions: {
...options,
headers,
}
}
return makeOperation(operation.kind, operation, context)
}
case 'endpoint-b': {
const headers = {
...options.headers,
Authorization: `Bearer ...`,
}
const context = {
...operation.context,
url: 'endpoint-b-url',
fetchOptions: {
...options,
headers,
}
}
return makeOperation(operation.kind, operation, context)
}
default: {
throw new Error(`Unexpected object: ${clientName}`)
return operation
}
}
}
TypeScript

Ajoutez autant de cas que de sources de données nécessaires.

Pour

getAuth
dans notre exemple ce sera simple :

getAuth: null
TypeScript

Si besoin, vous pouvez y ajouter de la logique, comme récupérer un token avec une promesse par exemple :

getAuth: async () => {
const token = await getToken()

return { token }
},
addAuthToOperation({ authState, operation }) {
const token = authState.token
// ...
}
TypeScript

Exemple de requête avec le client GraphQL

Pour utiliser la bonne API avec le client GraphQL :

function getDataFromEndpointA() {
const { data } = await client.query(myQueryForEndpointA, {}, {
clientName: 'endpoint-a',
}).toPromise()

return data
}

function getDataFromEndpointB() {
const { data } = await client.query(myQueryForEndpointB, {}, {
clientName: 'endpoint-b',
}).toPromise()

return data
}
TypeScript

Exemple de requête avec les hooks React

Pour utiliser la bonne source en passant par une intégration du client Urql (comme via les hooks React) :

const [{ error, data, fetching }] = useQuery(
myQueryForEndpointA,
{} // cet objet contient les variables de la requête,
context: useMemo(() => ({
clientName: 'endpoint-a'
}), []),
})

const [{ error, data, fetching }] = useQuery(
myQueryForEndpointB,
{} // cet objet contient les variables de la requête,
context: useMemo(() => ({
clientName: 'endpoint-b'
}), []),
})
TypeScript

Note : pensez à utiliser

useMemo
quand vous définissez le contexte pour éviter de rentrer dans une boucle infinie avec React. (voir l'issue sur Github)

Conclusion

Vous devriez maintenant avoir suffisamment d’informations pour mettre en place un client Urql qui gère plusieurs sources de données.

Notes :

  • le code actuel n’est pas 100% « type safe » puisque la clé

    clientName
    n'existe pas dans le typage Typescript du contexte Urql,

  • ajouter une source de donnée augmente la complexité dans votre gestion des typages si vous utilisez

    graphql-codegen
    . On en parlera dans un prochain article.

Pour vous simplifier la vie, j’ai créé ce Gist avec l’ensemble des bouts de code de l’article. N'hésitez pas à y ajouter un commentaire si vous souhaitez apporter votre avis ou votre aide.

Et si jamais vous avez la moindre question (ou si vous souhaitez juste discuter), vous pouvez me contacter via Twitter.

0 vues 
Note : 4.1/5 avec plus de 2 votes.
Émettre un avisPartager sur Twitter

FAQ en rapport avec l'article

  • Qu'est-ce qu'Urql ?

    Urql est un client GraphQL hautement personnalisable. C'est une alternative, plus légère, fonctionnelle et facile à utiliser, à Apollo Client. Il est pensé pour être à la fois facile à utiliser pour les novices de GraphQL, et à la fois prêt pour supporter des applications nécessitant une infrastructure GraphQL plus poussée.

  • Qui suis-je ?

    Je m'appelle Antoine Lin, j'ai 25 et je vis près de Paris. J'ai grandi en Seine-et-Marne et j'ai fait mes études à l'école HETIC, Montreuil. Je suis développeur indépendant, et depuis 2015, j'accompagne des marques et des influenceurs en leur livrant des expériences web sur-mesure.

  • Quelle est mon activité ?

    Je suis développeur front-end, je m'occupe de créer des expériences web (de la création du parcours utilisateur, à la connexion avec des services en passant par l'intégration des interfaces). Il m'arrive de faire du back-end et du devops au besoin aussi.

Les dernières publications

À propos
Développement web
Ouverture de mon blog !
18 May 2021 — 16 min de lecture
vues

Que ce soit du choix de la plateforme, à la façon dont je gère mes données avec DatoCMS, ou pour parler des diverses difficultés que j'ai pu rencontrer. Je reviens, dans cet article, sur les détails de l'ouverture de mon blog, antoinelin.com.