Démarrer avec Vue.js, Apollo, GraphQL, Hasura et PostgreSQL

Vue.js, Apollo, GraphQL, Hasura, PostgreSQLVoici comment démarrer tranquillement une application avec un front Vue.js qui interroge une API GraphQL.

Le tout sans faire trop d’effort à l’aide d’Hasura et d’Apollo.

Ça fait pas mal de technos :

  • Vue.js : framework front JS
  • Apollo : composant qui permet d’interroger une API GraphQL depuis le front
  • GraphQL : type d’API créé par Facebook en 2012 (différent de REST et SOAP)
  • Hasura : outil open-source qui permet d’exposer une API GraphQL depuis une base de donnée PostgreSQL
  • PostgreSQL : le SGBD qui va stocker les données

Pour re-resumé : en installant l’outil Hasura sur un serveur PostgreSQL, on expose une API de type GraphQL à moindre effort.

Ensuite on peut interroger cette API depuis n’importe quel front, en l’occurrence on va se concentrer sur Vue.js et le composant Apollo (on peut imaginer qu’Apollo est à GraphQL/JS ce que PDO_MYSQL est à MySQL/PHP).

L’API GraphQL permet de récupérer des données plus finement et en une seule requête, contrairement à une API REST qui va nécessiter une requête par type de donnée.

GraphQL API

Pour l’exemple on va utiliser des données data.gouv.fr : la liste des équipements sportifs en France.

Lancer Hasura sur sa machine

Créer un docker-compose qui charge Hasura et une base PostgreSQL, pour cela deux solutions :

  1. ajouter un service postgres dans le docker-compose (si on n’a pas Postgres en local)
  2. créer une base local Postgres sur sa machine

Version tout packagée avec PostgreSQL intégré à la stack Docker
Fichier docker-compose.yaml

version: '3.6'
services:
  postgres:
    image: postgres
    ports:
    - "5432:5432"
    restart: always
    volumes:
    - ./db_data:/var/lib/postgresql/data
  graphql-engine:
    image: hasura/graphql-engine:v1.1.0
    ports:
    - "8080:8080"
    depends_on:
    - "postgres"
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@postgres:5432/postgres
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      #HASURA_GRAPHQL_ADMIN_SECRET: secret
volumes:
  db_data:

Version avec PostgreSQL installé en local
Fichier docker-compose.yaml

version: '3.6'
services:
  graphql-engine:
    image: hasura/graphql-engine:v1.1.0
    ports:
    - "8080:8080"
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://where_to_sport:secret@172.17.0.1:5432/where_to_sport
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      #HASURA_GRAPHQL_ADMIN_SECRET: secret

Manipulation à faire sur PostgreSQL (pour la solution 2)

Création d’un base de donnée :

CREATE DATABASE where_to_sport;

Hasura nécessite l’extension pgcrypto pour fonctionner :

CREATE EXTENSION IF NOT EXISTS pgcrypto;

Création d’un schéma :

CREATE SCHEMA app;

Création d’un user pour accéder à la BDD :

CREATE ROLE where_to_sport LOGIN password 'secret';
GRANT ALL ON DATABASE where_to_sport TO where_to_sport;

Ajout des droits sur la BDD :
Fichier /etc/postgresql/11/main/pg_hba.conf (remplacer 11 par votre version de PostgreSQL).

host    where_to_sport  where_to_sport  0.0.0.0/0         md5

Lancer la stack Docker :

docker-compose up -d

Accéder à Hasura : http://localhost:8080/

I want data !

On récupère le CSV sur data.gouv

On l’importe via un outil ou script (DataGrip par exemple).
On s’ajoute deux tables de liaison pour l’exemple (equipements_type qui contient le code équipement ainsi que son libellé, et une table departements).

Data model PostgreSQL

Une fois que notre schéma est ok, il faut indiquer à Hasura quelle tables “tracker” et les relations entre elles (il les devine tout seul, il faut juste les activer).

Hasura data 1 Hasura data 2

On peut ensuite s’amuser avec l’outil intégré à Hasura (GraphiQL) pour faire nos premières requêtes :

Hasura GraphiQL

Il reste maintenant à démarrer une application Vue.js et interroger cette API de manière dynamique.

L’outil Visual Studio Code dispose de plugins GraphQL, Apollo, Vue.js etc.

Vue.js

Initier un projet Vue.js avec ce dont vous avez besoin (node-sass, babel, router, eslint…).

npm run serve

La page d’accueil par défaut s’affiche sur http://localhost:8081/

Ajouter le composant Apollo au projet :

vue add apollo

Créer un fichier .env à la racine du projet et ajouter l’url de l’API GraphQL exposé par Hasura :

VUE_APP_GRAPHQL_HTTP=http://localhost:8080/v1/graphql
VUE_APP_GRAPHQL_WS=ws://localhost:8080/v1/graphql

ws:// est le Websocket utilisé, il permet au back de pusher automatiquement les modifications de data au front, qui pourra ensuite les afficher en temps réel (comme un tchat).

Vue.js GQLDans l’arborescence du projet Vue.js, plutôt que stocker les requêtes des données dans le dossier store, on va les stocker dans un dossier graphql.

C’est ici que seront organisé nos requêtes GraphQL, celles qui sont générées par l’outil GraphiQL d’Hasura.

Un fragment fonctionne de la même manière qu’un include, par exemple si vous avez 10 requêtes qui concernent des Users, et que vous récupérez systématiquement les mêmes champs (id, name, email), plutôt que de réécrire ces champs X fois il est préférable de les avoir dans un fragment qui sera réutilisable.

Voila à quoi ressemble la requête qui récupère les départements :

query GetDepartements {
  app_departements(order_by: {libelle: asc}) {
    id
    libelle
  }
}

Puis le fragment de ma recherche (equipement_type et departement étant les données des tables liées) :

fragment equipementsFragment on app_equipements {
  id
  cominsee
  comlib
  insnom
  gestiontypeproprietaireprinclib
  equnom
  equdouche
  equanneeservice
  equnbplacetribune
  naturesollib
  naturelibelle
  equnbvestiairespo
  equaccueilbuvette
  equgpsx
  equgpsy
  equdatemaj
  equipement_type {
    equipementtypecode
    equipementtypelib
  }
  departement {
    libelle
  }
}

Et enfin la récupération d’équipements avec les deux critères de recherche et l’import du fragment qui s’écrit ...equipementsFragment :

#import "./EquipementsFragment.gql"

query GetAllEquipements($departement_id: Int, $equipement_code: Int) {
  app_equipements(
      where: {
        departement: {id: {_eq: $departement_id}},
        _and: {
          equipement_type: {equipementtypecode: {_eq: $equipement_code}}
          }
        },
      order_by: {
        comlib: asc,
        equipement_type: {equipementtypelib: asc},
        insnom: asc
      }
    ) {
    ...equipementsFragment
  }
}

Il faut noter que ces requêtes sont uniquement des query (équivalent du SELECT), il existe également les mutation (INSERT, UPDATE, DELETE), et les subscription (permet de récupérer en live les modifications de données).

Pour pouvoir utiliser ces requêtes il faut les importer dans le composant (ou la vue) :

import GET_DEPARTEMENTS from '@/graphql/GetDepartements.gql'
import GET_TYPES_EQUIPEMENTS from '@/graphql/GetTypesEquipements.gql'
import SEARCH_EQUIPEMENTS from '@/graphql/SearchEquipements.gql'

Le composant Apollo permet d’interroger l’API avec ces requêtes de manière simple.

Voila le bout de code qui récupère la liste des départements et des équipements, puis qui gère la recherche à la validation du formulaire :

export default {
  name: 'Home',
  data () {
    return {
      departements: [],
      typesEquipements: [],
      resultats: null,
      searchVars: null,
      searchDepartementId: null,
      searchEquipementCode: null
    }
  },
  apollo: {
    app_departements: {
      query: GET_DEPARTEMENTS,
      result ({ data }) {
        this.departements = data.app_departements
      }
    },
    app_equipements_type: {
      query: GET_TYPES_EQUIPEMENTS,
      result ({ data }) {
        this.typesEquipements = data.app_equipements_type
      }
    },
    app_equipements: {
      query: SEARCH_EQUIPEMENTS,
      variables () {
        return {
          departement_id: this.searchDepartementId,
          equipement_code: this.searchEquipementCode
        }
      },
      skip () {
        return !this.searchVars || !this.searchVars.searchDepartementId || !this.searchVars.searchEquipementCode
      },
      result ({ data }) {
        this.resultats = data.app_equipements
      }
    }
  },
  methods: {
    search (e) {
      e.preventDefault()
      this.searchVars = {
        searchDepartementId: this.searchDepartementId,
        searchEquipementCode: this.searchEquipementCode
      }
    }
  }
}

L’instruction skip indique sous quelles conditions la requête ne doit pas être lancée, en l’occurrence si nos critères de recherches ne sont pas renseignés.

On ajoute à ça le plugin vue2-google-maps avec npm et on obtient une application de recherche d’équipements sportifs :

Application résultat 2Application résultat 1

Pour aller plus loin on peut ajouter des facettes afin de filtrer plus finement la recherche, agrémenter Google Maps de photos de lieux etc. :)

Ressources intéressantes :

Share Button

Laisser un commentaire.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.