1°/ Préambule

Dans une application SPA créée par exemple en vuejs, le principe de fonctionnement basique consiste à demander des données à une ou plusieurs API afin de modifier l'affichage de la fenêtre de navigation.

L'envoi de requêtes à une API se fait de façon asynchrone, de façon à ce que l'application ne soit pas bloquée le temps que la requête soit traitée et que la réponse arrive. Pour envoyer la requête, il y a 2 solutions très utilisées :

  • axios
  • fetch

Les deux solutions proposent des fonctions pour faire des requêtes http asynchrones. Il est donc possible d'interroger une API REST pour obtenir des données.

Elles ont chacune leur avantages/inconvénients, mais si l'on privilégie la portabilité et la simplicité d'utilisation, Axios a un léger avantage. En revanche, Axios est à l'origine un module node, donc du code JS. Utiliser Axios implique d'intégrer son code l'application front-end. Quand celle-ci est envoyée au navigateur, elle sera donc plus volumineuse que la même application faite avec fetch, dont les fonctions sont déjà intégrées dans les navigateurs modernes (mais pas les anciens !)

 

2°/ Utilisation basique d'axios

Pour utiliser axios dans une application JS, il  suffit d'installer le paquet axios :

npm install axios

 

Ensuite, dans les fichiers JS où l'on a besoin d'axios, il faut importer un objet qui permet d'accéder à toutes les fonctionnalités d'axios :

import axios from 'axios'

 

Enfin, on utilise les méthode de cet objet pour faire des requêtes asynchrones :

try {
  // requete GET : un seul paramètre = URL demandée
  let answer1 = await axios.get("https://monsite.org/products/get/12345")
  ...
  // requete POST : deux paramètres = URL demandée + données à envoyer
  let answer2 = await axios.post("https://monsite.org/products/create", { name: "enclume", price: 1000})
}
catch(err) {
  ...
}

 

ATTENTION ! les fonctions axios ne renvoient pas directement ce qui est renvoyé par l'API. Elles renvoient un objet dont le champ data contient la réponse de l'API. Dans l'exemple ci-dessus, la réponse de l'API est donc accessible grâce à answer.data.

 

3°/ Axios dans une application SPA

 

3.1°/ Créer un service axios

Dans le cadre d'un projet "professionnel", on n'utilise pas axios comme montré ci-dessus, en l'important dans les composants qui l'utilisent et en faisant des requêtes dans la partie <script>. On cherche avant tout la modularité et la réutilisabilité. C'est pourquoi, l'importation d'axios ne se fait qu'une seule fois, dans un fichier service, par exemple axios.service.js, que l'on met avec les autres fichiers de service.

Dans ce fichier, on va spécifier une (ou plusieurs) configuration d'utilisation d'axios commune à un ensemble de requêtes. Par exemple, il est possible de fixer une URL de base pour ces requêtes, ce qui évite de la fournir à chaque appel des fonctions qui font les requêtes HTTP. On peut également inclure des entêtes http, des cookies, etc. Pour chaque configuration voulue, on crée un "agent axios" auquel on donne un objet JSON décrivant la configuration. Si le front-end ne s'adresse qu'à une seule API, on ne crée généralement qu'un seul agent.

 

Exemple de fichier axios.service.js basique :

import axios from 'axios'

const axiosAgent = axios.create({
  baseURL: 'https://apidemo.iut-bm.univ-fcomte.fr/apidemo'
});

 

De plus, dans un cas simple mais très courant où un seul agent est créé et que sa configuration est valable pour toutes les requêtes vers l'API, on peut également créer des fonctions outils, permettant d'exécuter les requêtes HTTP courantes, ainsi qu'une fonction permettant de traiter les cas d'erreurs. Ces fonctions outils vont "masquer" l'utilisation d'axios car ce sont elles qui sont exportées et utilisées par le reste de l'application. 

...
// name is used for debug messages
function postRequest(uri, data, name) {
  let answer = null
  try {
    answer = await axiosAgent.post(uri, data)
  } catch (err) { 
    answer = handleError(err, name);
  }
  return answer.data;
}
...
export {
  postRequest,
}
 
Dans cet exemple (simplifié par rapport à la démonstration), on suppose que si la requête axios échoue, on appelle une fonction de traitement de l'erreur qui va retourner un objet qui a la même structure que si la requête avait réussi, donc avec un champ data contenant, ce qui est retourné par l'API. Cela suppose bien entendu que l'API elle-même utilise une seule structure pour répondre à une requête positivement (= pas d'erreur) ou négativement (= erreur).

 

3.2°/ Utilisation du service axios

Toujours pour assurer une grande modularité/réutilisabilité, les composants n'utilisent JAMAIS le service axios et les fonctions exportées. Ce sont les autres services qui vont utiliser le service axios en définissant des fonctions pour interroger l'API. Généralement, on fait une fonction par route possible sur l'API, mais ce n'est pas une obligation.

Pour aller encore plus loin dans la modularité et permettre de développer le front en même temps que le back-end, il est conseillé de créer une fonction "wrapper", qui appelle soit la fonction qui utilise l'API, soit celle qui utilise la source locale. C'est ce wrapper qui est exporté et disponible au reste de l'application. Cette structuration permet de commencer le développement avec une source de données lcoale, comme un fichier, et de basculer facilement vers une source de type API quand celle-ci est implémentée.

Exemple (tiré de la démonstration) dans towns.service.js :

import {getRequest} from "@/services/axios.service";
import LocalSource from "@/datasource/controller"

async function getAllTownsFromAPI() {
  return getRequest('/towns/get', 'GETALLTOWNS')
}

async function getAllTownsFromLocalSource() {
  return LocalSource.getAllTowns()
}

async function getAllTowns() {
  let answer = await getAllTownsFromAPI()
  //let answer = await getAllTownsFromLocalSource(id)
  return answer
}

export {
  getAllTowns
}

 

3.3°/ Utilisation des services dépendants d'axios

Si on suit l'organisation donnée ci-dessus, les composants du front-end peuvent importer les fonctions des différents services afin de récupérer des données, sans même savoir si elles proviennent de l'API ou de la source locale = modularité/réutilisabilité maximale. Reste cependant une question : où doit-on importer et appeler ces fonctions ?

La réponse est simple et considère seulement deux cas : si les données récupérées grâce à une fonction d'un service sont utilisées 

  • par un seul et unique composant => on importe et appelle la fonction directement dans ce composant.
  • dans plusieurs composants => on importe la fonction dans le store et on l'appelle dans une action qui stocke les données reçues dans le state. Les composants utilisent ensuite les mappers pour accéder à l'action en question et au state.

Exemple (tiré de la démonstration) dans le cas du store :

import Vue from 'vue'
import Vuex from 'vuex'
import {getAllTowns} from "@/services/towns.service";
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    towns: [],
  },
  mutations: {
    setTowns(state, towns) {
      state.towns.splice(0)
      towns.forEach(t => state.towns.push(t))
    },
},
  actions: {
    async getTowns({commit}) {
      console.log("STORE: get all towns")
      let result = null
      try {
        result = await getAllTowns()
        if (result.error === 0) {
          commit('setTowns',result.data)
        }
        else {
          console.log(result.data)
        }
      }
      catch(err) {
        console.log("Cas anormal dans getTowns()")
      }
    },
  }
}

 

 4°/ Démonstration

Le code de l'application est téléchargeable [ ici ].

Il ne contient que le répertoire src des sources. Il faut donc au préalable créer un projet vide, avec seulement vuex comme plugin. Une fois le projet créé, il faut encore ajouter axios, puis remplacer le répertoire src avec celui dans l'archive.