Cours JavaScript et React · Module 11: Récupération de données et API

Axios et les bibliothèques HTTP

8 min de lecture
Dans cette leçon7

Ton vaisseau envoie déjà des requêtes au Centre de contrôle avec fetch(), mais tu remarques vite le travail répétitif : à chaque requête, tu vérifies response.ok à la main, tu parses le JSON à la main, et tu retapes la même adresse de serveur et le même token dans chaque fichier. Le fetch() intégré est un émetteur radio de base. Quand la flotte grandit, il nous faut un système de communication avec des filtres, une limite de temps et un centre de configuration unique. C'est là que nous faisons appel à Axios, l'une des bibliothèques HTTP les plus populaires pour JavaScript (la version actuelle est Axios 1.x).

Pourquoi Axios plutôt que fetch ?

Le plus simple pour voir la différence, c'est la même requête écrite de deux façons. Remarque combien de lignes il faut à fetch() pour se comporter de manière sûre :

1// FETCH - nécessite une gestion manuelle
2const response = await fetch(url);
3if (!response.ok) throw new Error('Erreur'); // fetch ne lève PAS d'erreur pour les codes 4xx/5xx !
4const data = await response.json(); // il faut parser le JSON manuellement
5
6// AXIOS - plus simple et plus sûr
7const { data } = await axios.get(url); // JSON automatique, lève des erreurs pour les codes 4xx/5xx

Les deux versions récupèrent les mêmes données, et le serveur ne voit aucune différence. Seule la quantité de travail change pour toi : fetch() ne rejette sa Promise qu'en cas d'erreur réseau, donc tu dois attraper une 404 toi-même, alors qu'Axios considère par défaut tout statut hors de la plage 2xx comme une erreur.

Les différences clés dans un seul tableau :

Fonctionnalitéfetch()Axios
Parsing JSONManuel (.json())Automatique
Erreurs HTTPNe lève pas pour 4xx/5xxLève automatiquement
IntercepteursAucunIntégrés
TimeoutPas d'option, mais il existe AbortSignal.timeout(ms)Option timeout
AnnulationAbortControllerAbortController (signal), CancelToken est déprécié
TransformationManuelleIntégrée

Installer et utiliser Axios

Tu installes Axios comme n'importe quel paquet, puis tu appelles des méthodes qui portent le nom des méthodes HTTP. L'objet réponse contient un champ data avec les données prêtes à l'emploi, status avec le code et headers avec les en-têtes :

1// Installation : npm install axios
2
3import axios from 'axios';
4
5// GET - récupération de données
6const response = await axios.get(
7  'https://api.space-center.com/planets'
8);
9console.log(response.data);   // données (déjà parsées !)
10console.log(response.status); // 200
11console.log(response.headers); // en-têtes
12
13// POST - envoi de données (sérialisation JSON automatique)
14const newMission = await axios.post(
15  'https://api.space-center.com/missions',
16  { name: 'Alpha Centauri', crew: 5 }
17);
18
19// PUT, DELETE
20await axios.put(url, updatedData);
21await axios.delete(url);

Avec axios.post, tu passes un objet ordinaire, et Axios le transforme lui-même en JSON et définit l'en-tête Content-Type. L'adresse du serveur, en revanche, se répète encore à chaque ligne, et c'est ce que nous corrigeons maintenant.

Créer une instance d'API

La méthode axios.create() crée un client séparé avec sa propre configuration : une adresse de base (baseURL), une limite de temps en millisecondes (timeout) et des en-têtes par défaut. C'est comme régler la radio sur la fréquence fixe du Centre de contrôle :

1// On crée un client API avec une configuration par défaut
2const spaceApi = axios.create({
3  baseURL: 'https://api.space-center.com',
4  timeout: 10000, // 10 secondes
5  headers: {
6    'Content-Type': 'application/json',
7    'X-Station-Id': 'ISS-2150',
8  },
9});
10
11// Maintenant on utilise des chemins plus courts
12const planets = await spaceApi.get('/planets');
13const missions = await spaceApi.get('/missions');
14const newMission = await spaceApi.post('/missions', {
15  name: 'Deep Space Survey',
16  destination: 'Proxima Centauri',
17});

Désormais, des chemins courts comme /planets suffisent. L'objet global axios n'a pas changé, donc d'autres parties de l'application peuvent avoir leurs propres instances pour d'autres serveurs. Je te recommande une instance par API, rangée dans son propre fichier.

Intercepteurs - les filtres de communication

Un intercepteur est une fonction qu'Axios exécute pour chaque requête avant son départ, ou pour chaque réponse avant qu'elle n'atteigne ton code. Ainsi, tu ajoutes le token à un seul endroit, et pas dans cent composants :

1// Intercepteur de requêtes - ajoute le token d'autorisation
2spaceApi.interceptors.request.use(config => {
3  const token = localStorage.getItem('authToken');
4  if (token) {
5    config.headers.Authorization = `Bearer ${token}`;
6  }
7  console.log('Envoi de la requête :', config.url);
8  return config;
9});

Un intercepteur de requêtes doit retourner config, sinon la requête ne part jamais. Le second filtre agit sur le chemin du retour : il prend une fonction pour le succès et une pour l'erreur, ce qui te permet de réagir globalement à une session expirée (401) ou à trop de requêtes (429) :

1// Intercepteur de réponses - gestion des erreurs
2spaceApi.interceptors.response.use(
3  response => {
4    // Succès - retourner les données
5    return response;
6  },
7  error => {
8    // Erreur - gestion globale
9    if (error.response?.status === 401) {
10      console.log('Session expirée - redirection vers la connexion');
11      window.location.href = '/login';
12    }
13    if (error.response?.status === 429) {
14      console.log('Trop de requêtes - réessaie plus tard');
15    }
16    return Promise.reject(error);
17  }
18);

Le Promise.reject(error) final est important : sans lui, l'erreur serait « avalée » et le composant croirait que tout s'est bien passé.

Transformation des requêtes et des réponses

Les options transformRequest et transformResponse te permettent de remodeler les données juste avant l'envoi et juste après la réception. Ici, on ajoute un horodatage à chaque requête et un champ fetchedAt à chaque réponse :

1const api = axios.create({
2  baseURL: 'https://api.space-center.com',
3  // Transformation avant l'envoi
4  transformRequest: [(data) => {
5    // Ajouter un timestamp à chaque requête
6    return JSON.stringify({
7      ...data,
8      timestamp: Date.now(),
9      source: 'space-station',
10    });
11  }],
12  // Transformation après la réception
13  transformResponse: [(data) => {
14    const parsed = JSON.parse(data);
15    // Ajouter un champ "fetchedAt" à chaque réponse
16    return { ...parsed, fetchedAt: new Date().toISOString() };
17  }],
18});

Attention : ton propre tableau remplace les transformations par défaut d'Axios, qui transforment normalement un objet en JSON et parsent la réponse. C'est pourquoi on appelle nous-mêmes JSON.stringify et JSON.parse ici. Pour de simples ajouts, les intercepteurs sont souvent plus pratiques, car ils ne désactivent rien par défaut.

Gestion des erreurs avec Axios

Comme Axios lève des erreurs pour les codes 4xx et 5xx, l'objet erreur t'indique à quelle étape ça a cassé. error.response existe quand le serveur a répondu, error.request quand la requête est partie mais qu'aucune réponse n'est revenue :

1async function fetchPlanet(id) {
2  try {
3    const { data } = await spaceApi.get(
4      `/planets/${id}`
5    );
6    return data;
7  } catch (error) {
8    if (error.response) {
9      // Le serveur a répondu avec un code d'erreur (4xx, 5xx)
10      console.error('Statut :', error.response.status);
11      console.error('Données :', error.response.data);
12    } else if (error.request) {
13      // Requête envoyée, mais pas de réponse (timeout, réseau)
14      console.error('Pas de réponse du serveur');
15    } else {
16      // Erreur dans la configuration de la requête
17      console.error('Erreur de configuration :', error.message);
18    }
19    throw error;
20  }
21}

Le statut 404 se trouve donc dans error.response.status, et non dans error.message. À la fin, on relance l'erreur pour que le composant puisse afficher un message.

Axios dans un composant React

Dans un composant, on assemble tout : l'instance, les états de chargement et d'erreur, et l'annulation de la requête quand le composant quitte l'écran. Dans du code plus ancien, tu verras CancelToken utilisé pour cela :

1// DÉPRÉCIÉ (depuis Axios 0.22) - tu le croiseras dans d'anciens projets
2const source = axios.CancelToken.source();
3spaceApi.get('/people/', { cancelToken: source.token });
4source.cancel('Composant démonté');

La documentation d'Axios marque CancelToken comme déprécié, donc dans du nouveau code on utilise l'AbortController standard et l'option signal, la même que celle de fetch() :

1import React, { useState, useEffect } from 'react';
2import axios from 'axios';
3
4const spaceApi = axios.create({
5  baseURL: 'https://swapi.dev/api',
6  timeout: 8000,
7});
8
9function CrewList() {
10  const [crew, setCrew] = useState([]);
11  const [loading, setLoading] = useState(true);
12  const [error, setError] = useState(null);
13
14  useEffect(() => {
15    const controller = new AbortController();
16
17    async function loadCrew() {
18      try {
19        const { data } = await spaceApi.get('/people/', {
20          signal: controller.signal,
21        });
22        setCrew(data.results);
23      } catch (err) {
24        if (!axios.isCancel(err)) {
25          setError(err.message);
26        }
27      } finally {
28        setLoading(false);
29      }
30    }
31
32    loadCrew();
33    return () => controller.abort();
34  }, []);
35
36  if (loading) return <p>Scan de l'équipage...</p>;
37  if (error) return <p>Erreur : {error}</p>;
38
39  return (
40    <ul>
41      {crew.map(member => (
42        <li key={member.name}>{member.name}</li>
43      ))}
44    </ul>
45  );
46}

axios.isCancel(err) reconnaît une annulation (Axios la signale comme une CanceledError), donc on ne l'affiche pas comme une erreur. Le reste du composant n'a pas changé : les états et le rendu conditionnel ressemblent exactement à ceux de fetch(). Nous les verrons en détail dans la prochaine leçon, et l'annulation dans la leçon sur les race conditions.

Retiens : Axios, c'est la station radio de la passerelle, une configuration, des filtres partagés et des erreurs claires pour toute la flotte.

Code de cette leçon : App.jsx
1import React, { useState, useEffect } from 'react';
2
3// Simulation de l'API Axios (dans un vrai projet : import axios from 'axios')
4const axios = {
5  create: (config) => {
6    const instance = {
7      defaults: config,
8      interceptors: {
9        request: { handlers: [], use: (fn) => instance.interceptors.request.handlers.push(fn) },
10        response: { handlers: [], use: (ok, err) => instance.interceptors.response.handlers.push({ ok, err }) },
11      },
12      get: async (path) => {
13        const url = config.baseURL + path;
14        // Exécute les intercepteurs de requête
15        let reqConfig = { url, headers: { ...config.headers } };
16        for (const handler of instance.interceptors.request.handlers) {
17          reqConfig = handler(reqConfig);
18        }
19        console.log('[Axios Request]', reqConfig.url, reqConfig.headers);
20
21        const response = await fetch(url, {
22          headers: reqConfig.headers,
23          signal: reqConfig.signal,
24        });
25        if (!response.ok) {
26          const error = new Error(`HTTP ${response.status}`);
27          error.response = { status: response.status, data: null };
28          // Exécute les intercepteurs d'erreur
29          for (const handler of instance.interceptors.response.handlers) {
30            if (handler.err) handler.err(error);
31          }
32          throw error;
33        }
34        const data = await response.json();
35        const result = { data, status: response.status, headers: response.headers };
36        // Exécute les intercepteurs de succès
37        for (const handler of instance.interceptors.response.handlers) {
38          if (handler.ok) handler.ok(result);
39        }
40        return result;
41      },
42    };
43    return instance;
44  },
45};
46
47// Nous créons une instance d'API
48const spaceApi = axios.create({
49  baseURL: 'https://swapi.dev/api',
50  timeout: 10000,
51  headers: { 'X-Station-Id': 'ISS-2150' },
52});
53
54// Intercepteur de requête - logging
55spaceApi.interceptors.request.use(config => {
56  console.log('[Intercepteur] Ajout d\'un timestamp à la requête');
57  config.headers['X-Request-Time'] = new Date().toISOString();
58  return config;
59});
60
61// Intercepteur de réponse
62spaceApi.interceptors.response.use(
63  response => { console.log('[Intercepteur] Succès :', response.status); },
64  error => { console.log('[Intercepteur] Erreur :', error.response?.status); }
65);
66
67function AxiosDemo() {
68  const [planets, setPlanets] = useState([]);
69  const [loading, setLoading] = useState(true);
70  const [error, setError] = useState(null);
71  const [logs, setLogs] = useState([]);
72
73  useEffect(() => {
74    async function loadPlanets() {
75      try {
76        setLogs(prev => [...prev, 'Envoi de la requête via Axios...']);
77        const { data } = await spaceApi.get('/planets/');
78        setPlanets(data.results);
79        setLogs(prev => [...prev, 'Données récupérées ! (JSON automatique)']);
80      } catch (err) {
81        setError(err.message);
82        setLogs(prev => [...prev, 'Axios a levé une erreur : ' + err.message]);
83      } finally {
84        setLoading(false);
85      }
86    }
87    loadPlanets();
88  }, []);
89
90  return (
91    <div className="app">
92      <h1>Axios - Bibliothèque HTTP</h1>
93      <div className="logs">
94        <h3>Logs des intercepteurs :</h3>
95        {logs.map((log, i) => <p key={i} className="log">{log}</p>)}
96      </div>
97      {loading && <div className="loading"><div className="spinner"></div></div>}
98      {error && <div className="error"><p>{error}</p></div>}
99      {!loading && !error && (
100        <div className="planet-list">
101          {planets.map(planet => (
102            <div key={planet.name} className="planet-card">
103              <h3>{planet.name}</h3>
104              <p><strong>Climat :</strong> {planet.climate}</p>
105              <p><strong>Population :</strong> {planet.population}</p>
106            </div>
107          ))}
108        </div>
109      )}
110    </div>
111  );
112}
113
114export default AxiosDemo;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quelle est la différence clé entre Axios et le fetch() intégré dans la gestion des erreurs HTTP ?

  2. 2. À quoi servent les intercepteurs dans Axios ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Récupérer des données avec Axios

  • Clique dans l'ordre

    Clique sur les éléments dans l'ordre pour créer une instance Axios configurée :

  • Tri vertical

    Ordonne les étapes pour ajouter un intercepteur de requête dans Axios :

Articles utiles