Les lives queries pour une application web réactive - Partie 2
Introduction
Dans l'article précédent : Les lives queries pour une application web réactive - Partie 1, nous avons construit la partie serveur de notre full stack web app. Dans cet article, nous allons réaliser la partie client.
Un bref récapitulatif de la partie 1 : Nous avons créé une base de données PostgreSQL
. Nous avons également créé un server node-express
lequel se connecte à notre base de données. Ensuite, nous avons ajouté knex-migrate
dans le but de maintenir notre schéma DB et permettre d'insérer des données dans notre base de donées. Finalement, nous avons ajouté PostGraphile
afin de générer un client GraphQL
via graphiql
avec toutes les opérations CRUD.
Pour la couche client, nous utiliserons les technologies suivantes :
- React + TypeScript
- GraphQL
- Apollo
C'est parti!
##Création du client React - TypeScript React est une librairie JavaScript permettant de créer des interfaces utilisateurs, et TypeScript est un superset typé de JavaScript. En utilisant cette combinaison, nous pouvons essentiellement construire nos IHMs en utilisant une version typée de JavaScript.
Pour démarrer, lançons la commande suivante depuis le dossier principal de notre stack applicative. Cela créera l'application react avec TypeScript.
npm create react-app client --template typescript
De part notre utilisation de GraphQL
, Apollo Client
est un bon candidat. Apollo Client
est une librairie JavaScript
permettant la gestion complète du state
. Dès que nous écrivons une requête GraphQL
, Apollo Client
veillera à effectuer la requête ainsi que la mise en cache des données, tout en mettant à jour notre IHM.
Nous avons besoin de quelques packages pour définir notre architecture. Voici la commande permettant l'installation de ces packages :
yarn add @apollo/client @apollo/react-components @apollo/react-hoc @types/graphql graphql graphql-tag
Nous utiliserons apollo-hooks
pour utiliser les subscriptions
GrapQL. Avant cet étape, structurons notre application comme ceci :
/src
- components
- graphql
- query
- mutations
Le dossier components
contiendra nos composants react. Sous le dossier graphql
, le dossier query
contiendra toutes nos requêtes (queries
+ subscriptions
) GraphQL
. Quant aux mutations, elles se trouveront dans le dossier mutations
. \
Intégration du CLI GraphQL Codegen
Encore un outil de plus!? Qu'est-ce donc ?
GraphQL Code Generator
est un outil de type CLI pouvant générer tous les typages TypeScript
d'un schéma GraphQL
. GraphQL Code Generator
permet de spécifier les scripts devant être exécutés selon certains évènements définis. (Dans notre cas, ce sera des hooks
basés sur des subscriptions
.) Afin de pouvoir utiliser GraphQL
en tant que hooks
pour nos queries
, mutations
et subscriptions
, nous devons implémenter graphql-code-generator
. L'installation se fait comme ceci :
yarn add -D @graphql-codegen/cli
Installons ensuite la bonne configuration en exécutant la commande suivante :
yarn graphql-codegen init
Cela lancera le CLI wizard. Ensuite, suivons les étapes listés ci-dessous :
- L'application est faite en React.
- L'adresse du schéma est la suivante : http://localhost:8080/graphql
- Adresse de nos opérations et fragments :
./src/components/**/*.tsx
. Cela recherchera tous les fichiersTypeScript
pour la déclarations des requêtes. - Utilisation des plugins par défaut :
TypeScript
,TypeScript Operations
,TypeScript React Apollo
. - Mettez à jour la destination des composants générés à
src/generated/graphql.tsx
(.tsx est requis par le pluginreact-apollo
). - Ne pas générer de fichier d'introspection.
- Utiliser le fichier par défaut
codegen.yml
- Utiliser la valeur
codegen
pour démarrer le script. Cela créera un fichiercodegen.yml
à la racine du projet. Nous devons encore ajouter un répertoire à notre fichiercodegen.yml
étant donné que vous sauvegardons nosqueries
,mutations
etsubscriptions
de façon séparée. Nous devons également ajouter un élément de configuration supplémentaire :withHooks: true
. Cela génèrera également deshooks
React typés pour nosqueries
,mutations
etsubscriptions
. Ajoutons aussi l'entrée./src/graphql/**/*.ts
. Cest l'emplacement de nos requêtesGraphQL
.
Notre fichier de configuration codegen.yml
devrait renssembler à ceci :
overwrite: true
schema: "http://localhost:8080/graphql"
documents:
- "./src/components/**/*.tsx"
- "./src/graphql/**/*.ts"
generates:
src/generated/graphql.tsx:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-react-apollo"
config:
withHooks: true
Pour disposer de queries
, mutations
et subscriptions
basées sur les hooks
, nous avons ajouté leurs dossiers respectifs. Pour valider tout cela, faites un
npm i
Créons dès à présent une souscriptinos qui permettra de garder l'état de notre utilisateur. Faisons cela en créant un nouveau fichier getUsers.query.ts
dans le dosser src/graphql/query
. (Vous pouvez vous aider de http://localhost:8080/graphiql
pour récupérer la syntaxe correcte). Voici nos requêtes permettant de récupérer les utilisateurs et de maintenanir leurs états :
import gql from 'graphql-tag';
export default gql`
subscription getUsersSub {
allUsers {
nodes {
email
id
name
createdAt
}
}
},
query getUsers {
allUsers {
nodes {
email
id
name
createdAt
}
}
}
`;
A présent, positionnez-vous dans le dossier client
et exécutez la commande suivante :
yarn codegen
Cela a pour effet de générer le fichier src/generated/graphql.tsx
basé sur la configuration définie dans codegen.yml
. Lorsque nous ouvrons le fichier, nous voyons qu'il s'agit de type généré et requêtes de type react hooks
basées sur notre schéma et nos fichier de requête. Note : Nous devons exécuter la commande yarn codegen
chaque fois que nous ajouter une nouvelle query
, mutation
ou subscription
dans le dossier graphql
.
Utilisation de Queries, Mutations et Subscriptions dans le client
Nous avons maintenant des queries
, mutations
et subscriptions
basées sur les hooks
à notre disposition. Nous pouvons donc les utiliser pour afficher de la donnée. Voici le fichier d'exemple pour afficher nos utilisateurs, en utilisant le requête de type hook générée lors de l'étape précédente. Voici le contenu du composant User
. Créez le fichier User.tsx
dans le dosser src/components
. Voici son contenu :
import React, { useEffect } from 'react';
import { User } from '../generated/graphql';
import { useGetUsers } from '../utils/services';
const Users: React.FC = () => {
const { data, error, loading } = useGetUsers();
const users = loading ? [] : data.allUsers.nodes;
useEffect(() => {
if (error) {
console.log(error);
}
if (loading) {
console.log(loading);
}
}, [data, error, loading]);
return (
<>
<h2>Hello users,</h2>
{users.map((user: User, index: number) => (
<p key={`user_${index}`}>{user.name} </p>
))}
</>
);
};
export default Users;
Nous utilisons une classe utilitaire utils/services.ts
dont voici le contenu :
import {
useGetUsersSubSubscription
} from '../generated/graphql'
export const useGetUsers = useGetUsersSubSubscription
L'étape suivante consiste en la configuration du client Apollo
. Créez le fichier Apollo.tsx
dans le dossier src
. Voici son contenu :
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
import { HttpLink, InMemoryCache, split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { WebSocketLink } from '@apollo/client/link/ws';
const cache = new InMemoryCache();
const httpLink = new HttpLink({
uri: 'http://localhost:8080/graphql',
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:8080/graphql`,
options: {
reconnect: true,
},
});
// The split function takes three parameters:
//
// * A function that's called for each operation to execute
// * The Link to use for an operation if the function returns a "truthy" value
// * The Link to use for an operation if the function returns a "falsy" value
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client: ApolloClient<NormalizedCacheObject> = new ApolloClient({
cache,
link,
});
export default client;
Ensuite, nous pouvons importer le composant Users
ainsi que le composant ApolloClient
dans le fichier App.tsx
. Voici le résultat :
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Users from './components/Users';
import client from './ApolloClient'
import { ApolloProvider } from '@apollo/client';
const App: React.FC = () => {
return (
<ApolloProvider client={client}>
<div className='App'>
<header className='App-header'>
<img src={logo} className='App-logo' alt='logo' />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<Users />
</header>
</div>
</ApolloProvider>
);
};
export default App;
Le projet peut enfin être démarré! Mais avant cela, une toute dernière chose! Il convient de faire une petite modification dans le fichier de configuration de TypeScript
. Ouvre donc le fichier tsconfig.json
situé à la racine du projet et modifez la ligne suivante :
"strict": true,
Changez la valeur par false. Cette fois c'est la bonne. Démarrons l'application.
yarn start
Une page web devrait s'ouvir à l'adresse http://localhost:3000
. Voici le résultat :
Afin de vérifier que notre stack applicative fonctionne de bout en bout, allez dans PgAdmin
modifiez à nouveau la valeur du champ name
d'un enregistrement de la table users
. C'est magique, la valeur change automatiquement dans la vue!
Vous venez de mettre en place une application full stack utilisant les lives queries
Félications!
Conclusion et remerciements
Les lives queries
sont un outil vraiment intéressant pour les applications front-end. Spécialement utiles pour les applications utilisées en interne dans les entreprises. Prenez garde si vous ouvrez une application utilisant les lives queries
sur internet avec potentiellement des millions d'utilisateurs. La technologie est encore à ces débuts et il n'existe pas de norme ou de façon standard de l'implémenter.
Il convient également de bien comprendre la différence entre les lives queries
et les subscriptions
. Ce sont deux choses complètement différentes. PostGraphile
implémente les lives queries
via le mot-clé subscriptions
. Attention à bien faire la distinction entre les deux. J'ai ajouté quelques liens expliquant le concept. En résumé, partons du principe que les subscriptions
sont utilisées pour réagir à un évènement alors que les lives queries
sont utilisées pour réagir à un changement d'état d'une donnée. La solution présentée dans cette série d'article est bien mais est fortement couplée à la technologie PostgreSQL
. Il est possible d'ajouter une couche d'abstraction supplémentaire afin de rendre l'unité de persistance indépendante. Un outil existe pour cela et il se nomme Debezium
. Cette solution utilise des topics kafka
. J'ai ajouté le liens ci-dessous.
Merci à Pratik Agashe pour son article initial sur lequel je me suis très largement inspiré.
Liens utiles et lectures supplémentaires
https://www.youtube.com/watch?v=TUgYyWC22og&ab_channel=Postman
https://medium.com/open-graphql/graphql-subscriptions-vs-live-queries-e38302c7ab8e
https://hasura.io/learn/graphql/react/intro-to-graphql/4-watching-data-subscriptions/
https://www.howtographql.com/graphql-js/7-subscriptions/
https://github.com/graphql/graphql-playground
https://www.npmjs.com/package/@graphile/subscriptions-lds
https://www.graphile.org/postgraphile/live-queries/
https://www.apollographql.com/docs/graphql-subscriptions/setup/
https://www.apollographql.com/blog/how-to-use-subscriptions-in-graphiql-1d6ab8dbd74b/
https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/
https://www.apollographql.com/docs/react/data/subscriptions/
https://www.apollographql.com/docs/react/api/react/hooks/#options
https://www.apollographql.com/docs/react/data/queries/
https://github.com/dotansimha/graphql-code-generator
https://github.com/apollographql/apollo-tooling/
https://graphql-code-generator.com/docs/getting-started/development-workflow