2. Se préparer pour la production

Comme on l’a vu plus précédemment, il y a quelques étapes à respecter avant de présenter notre application au monde entier.

Et point le plus important que nous devons régler, c’est le fait que notre API Rest ne fonctionne que sur notre machine locale, mais pas en production ! Et oui, pour faire fonctionner notre API Rest, nous lançons la commande npm run start:api. Mais une fois déployé, notre application ne sera qu’un ensemble de fichiers HTML, CSS et JavaScript. Il faudrait déployer une API Rest sur un autre serveur… 🤯

Je vous propose donc une alternative plus intéressante dans notre cas.

React nous donne le moyen de savoir si nous sommes entrain de développer sur notre machine, ou si l’application est en production auprès de « vrais » utilisateurs. On peut récupérer cette information comme ceci :

// On est en plein développement !
const isDev = (!process.env.NODE_ENV || process.env.NODE_ENV === 'development'); 

// Par défaut, il n'y a que deux environnements.
// Donc si nous ne développons pas... c'est que nous sommes en production !
const isProd = !isDev; 

Ce n’est pas plus compliqué que ça. Par contre vous avez surement des questions à propos de cette étrange variable process.env.NODE_ENV.

La variable globale process.env est injectée par Node au moment de l’exécution de votre application, et elle représente l’état de l’environnement système dans lequel se trouve votre application au démarrage. Lorsque vous faites npm run start, c’est que vous allez développer. Et si vous lancer une autre commande npm run build que nous allons voir plus tard, c’est que vous êtes prêt à déployer votre application. CQFD. 👍

Du coup, si nous reprenons notre problème d’API, nous remarquons trois choses :

  • Tous les appels à notre API Rest se font depuis le service de pokémons pokemon-service.ts. Il n’y a donc qu’un seul fichier à adapter.
  • Nous devons appeler notre API Rest simulée uniquement dans l’environnement de développement.
  • En production, nous pouvons tout à fait retourner une promesse qui simule une API Rest. C’est vrai, rien ne nous en empêche, et nous pouvons nous faire aider d’une variable statique dans notre service pour sauvegarder temporairement l’état des pokémons dans notre application.

En prenant en compte ces trois considération, j’en arrive à la conclusion suivante. Pour chaque appel HTTP de mon service de pokémons, j’envoie une requête à mon API Rest en développement, et en production je retourne une Promesse avec la même réponse que mon API Rest. Après implémentation, j’obtiens ceci de mon côté pour le service pokemon-service.ts :

import Pokemon from "../models/pokemon";
import POKEMONS from "../models/mock-pokemon";

export default class PokemonService {

  static pokemons:Pokemon[] = POKEMONS;

  static isDev = (!process.env.NODE_ENV || process.env.NODE_ENV === 'development');

  static getPokemons(): Promise<Pokemon[]> {
    if(this.isDev) {
      return fetch('http://localhost:3001/pokemons')
      .then(response => response.json())
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {
      resolve(this.pokemons);
    });
  }

  static getPokemon(id: number): Promise<Pokemon|null> {
    if(this.isDev) {
      return fetch(`http://localhost:3001/pokemons/${id}`)
      .then(response => response.json())
      .then(data => this.isEmpty(data) ? null : data)
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {    
      resolve(this.pokemons.find(pokemon => id === pokemon.id));
    }); 
  }

  static updatePokemon(pokemon: Pokemon): Promise<Pokemon> {
    if(this.isDev) {
      return fetch(`http://localhost:3001/pokemons/${pokemon.id}`, {
        method: 'PUT',
        body: JSON.stringify(pokemon),
        headers: { 'Content-Type': 'application/json'}
      })
      .then(response => response.json())
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {
      const { id } = pokemon;
      const index = this.pokemons.findIndex(pokemon => pokemon.id === id);
      this.pokemons[index] = pokemon;
      resolve(pokemon);
    }); 
  }

  static deletePokemon(pokemon: Pokemon): Promise<{}> {
    if(this.isDev) {
      return fetch(`http://localhost:3001/pokemons/${pokemon.id}`, {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json'}
      })
      .then(response => response.json())
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {    
      const { id } = pokemon;
      this.pokemons = this.pokemons.filter(pokemon => pokemon.id !== id);
      resolve({});
    }); 
  }

  static addPokemon(pokemon: Pokemon): Promise<Pokemon> {
    pokemon.created = new Date(pokemon.created);

    if(this.isDev) {
      return fetch(`http://localhost:3001/pokemons`, {
        method: 'POST',
        body: JSON.stringify(pokemon),
        headers: { 'Content-Type': 'application/json'}
      })
      .then(response => response.json())
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {    
      this.pokemons.push(pokemon);
      resolve(pokemon);
    }); 
  }

  static searchPokemon(term: string): Promise<Pokemon[]> {
    if(this.isDev) {
      return fetch(`http://localhost:3001/pokemons?q=${term}`)
      .then(response => response.json())
      .catch(error => this.handleError(error));
    }

    return new Promise(resolve => {    
      const results = this.pokemons.filter(pokemon => pokemon.name.includes(term));
      resolve(results);
    });

  }

  static isEmpty(data: Object): boolean {
    return Object.keys(data).length === 0;
  }

  static handleError(error: Error): void {
    console.error(error);
  }
}

Je vous laisse regarde le code de votre côté, il n’y a rien de compliqué en soi.

Pour chaque appel, on respecte le même schéma. Est-ce qu’on est en développement ? Oui, alors on fait un appel à notre API Rest. Sinon, et bien on retourne une promesse avec une réponse similaire à notre API Rest.

Voici quelques explications supplémentaires :

  • À la ligne 6 : On définit une variable statique pour sauvegarder temporairement l’état des pokémons dans notre application.
  • À la ligne 8 : On retiens l’environnement dans lequel on se trouve.
  • À la ligne 11 : On vérifie si on est en développement. Si c’est le cas on fait un appel à notre API Rest.
  • De la ligne 17 à 19 : On retourne promesse avec une réponse similaire à notre API Rest.

Désormais, on a une application qui pourra récupérer les données correctement, que l’on soit dans l’environnement de développement ou de production !