1. Simuler une API Rest

Jusqu’à maintenant, nous stockions et récupérions nos données depuis la constante POKEMONS exportée depuis le fichier mock-pokemon.ts. Seulement ce système ne nous convient plus, et nous voulons pouvoir communiquer avec un serveur distant désormais !

Malheureusement, nous ne disposons pas d’un serveur pour gérer les requêtes de notre application de pokémons. D’ailleurs, il faudrait même suivre un autre cours, consacré exclusivement à cette tâche !

Mais alors, comment va-t-on faire ? 🥺

Je vous rassure, en tant que développeur, on a l’habitude de se débrouiller. On va utiliser une petite librairie qui nous permet de simuler une API Rest, qui fonctionnera comme si nous utilisions un serveur distant ! Cette simulation est possible grâce à la librairie json-server, qui est une des librairies les plus populaires pour mettre en place une petite API Rest sans trop d’efforts. Nous allons voir comment cela fonctionne.

D’abord, installez cette librairie miraculeuse avec la commande suivante :

npm install -g json-server

L’option -g permet d’installer cette librairie au niveau global sur votre poste de travail. Ainsi, vous pourrez réutiliser cette librairie entre vos différents projets.

Il est nécessaire de lancer cette commande en tant qu’administrateur sur votre poste de travail. Si vous êtes sur Windows, « clic droit > Exécuter en tant qu’administrateur ». Si vous êtes sur Mac ou Linux, préfixez la commande par « sudo « . Ensuite ça devrait mieux fonctionner.

Maintenant, nous avons la possibilité de simuler une API Rest. Cependant, il va bien falloir sauvegarder nos données de pokémons quelque part. Pour cela, la librairie json-server utilise un fichier dans votre projet, afin de sauvegarder tous les pokémons et les éventuelles modifications que nos utilisateurs pourraient apporter dessus.

On va donc créer une mini base de données. Créez donc un nouveau fichier db.json, dans le dossier models de notre projet :

{
  "pokemons": [
    {
      "id": 1,
      "name": "Bulbizarre",
      "hp": 25,
      "cp": 5,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/001.png",
      "types": ["Plante", "Poison"]
     },
     {
      "id": 2,
      "name": "Salamèche",
      "hp": 28,
      "cp": 6,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/004.png",
      "types": ["Feu"]
     },
     {
      "id": 3,
      "name": "Carapuce",
      "hp": 21,
      "cp": 4,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/007.png",
      "types": ["Eau"]
     },
     {
      "id": 4,
      "name": "Aspicot",
      "hp": 16,
      "cp": 2,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/013.png",
      "types": ["Insecte", "Poison"]
     },
     {
      "id": 5,
      "name": "Roucool",
      "hp": 30,
      "cp": 7,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/016.png",
      "types": ["Normal", "Vol"]
     },
     {
      "id": 6,
      "name": "Rattata",
      "hp": 18,
      "cp": 6,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/019.png",
      "types": ["Normal"]
     },
     {
      "id": 7,
      "name": "Piafabec",
      "hp": 14,
      "cp": 5,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/021.png",
      "types": ["Normal", "Vol"]
     },
     {
      "id": 8,
      "name": "Abo",
      "hp": 16,
      "cp": 4,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/023.png",
      "types": ["Poison"]
     },
     {
      "id": 9,
      "name": "Pikachu",
      "hp": 21,
      "cp": 7,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/025.png",
      "types": ["Electrik"]
     },
     {
      "id": 10,
      "name": "Sabelette",
      "hp": 19,
      "cp": 3,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/027.png",
      "types": ["Normal"]
     },
     {
      "id": 11,
      "name": "Mélofée",
      "hp": 25,
      "cp": 5,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/035.png",
      "types": ["Fée"]
     },
     {
      "id": 12,
      "name": "Groupix",
      "hp": 17,
      "cp": 8,
      "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/037.png",
      "types": ["Feu"]
     }
  ]
}

Contrairement aux apparences, ce fichier n’est pas un simple objet statique. Il va servir de base de données pour json-server, et c’est ce fichier qui contiendra les dernières données à jour de notre application.

De son côté, json-server met en place plusieurs points de terminaisons sur lesquels nous pourrons effectuer des requêtes :

// Exemple avec une collection de pokémons :
GET /pokemons // renvoie tous les pokémons 
GET /pokemons/1 // renvoie le pokémon avec l'identifiant 1 
POST /pokemons // ajoute un nouveau pokémon dans notre application
PUT /pokemons/1 // modifie le pokémon avec l'identifiant 1 
 
// Cette requête retourne les pokémons dont au moins une propriété contient le terme recherché :
GET api/pokemons?q=term // 'term' est terme de recherche.

Bien pratique n’est-ce pas ? Il est quasiment possible de faire toutes les requêtes possibles pour une petite application de démonstration : ajout, suppression, modification, recherche, etc. Nous allons voir prochainement comment faire tout cela.

Nous verrons également comment utiliser cette API, plutôt que les données « en dur » du fichier mock-pokemons.ts, depuis nos composants.

Mais pour le moment, notre API n’est pas encore prête à être utilisé, il faut encore la démarrer !

Hey, attend ! Pourquoi la date de création des pokémons a disparu ? 🧐

Bien vu, j’espérais que ça passerait inaperçu, mais vous avez l’oeil. La propriété created des pokémons a bel et bien disparue. En fait, à la base notre constante POKEMONS passé un nouvel objet dynamique : new Date(). Cela fonctionné très bien dans notre constante, mais un fichier JSON comme db.json ne fonctionne pas comme ça. C’est une structure statique de données, et il est impossible de calculer une date à la volée à l’intérieur. Du coup, soit on met une date « en dur », sous la forme d’une chaine de caractère comme « 19/12/1992 », soit on ne passe rien.

L’avantage de ne rien passer, c’est qu’on peut laisser notre méthode formatDate faire le travail à notre place, en définissant une valeur par défaut à la ligne 1. Je vous invite à modifier le fichier format-date.ts comme ceci :

const formatDate = (date: Date = new Date()): string => {
  return `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()}`;
}

Maintenant, si nous ne passons aucune valeur à cette fonction, alors elle retournera la date courante. C’est aussi bien comme ça, cela nous permet de nous concentrer sur la suite.