API REST
Ce TP vous permettra de créer une filmothèque pour rechercher les films que nous avons vus, et leur donner une note (un peu comme sur le site sens-critique).
Cette application sera sous forme d’une page web dynamique échangeant avec le serveur via une API REST.
Objectifs d’apprentissage
- Savoir ce qu’est une API REST et la différence avec une architecture MVC
- Créer une API REST avec express
- Utiliser
fetchpour appeler une API REST depuis le navigateur - Utiliser JavaScript pour créer une page dynamique
Sommaire
- Présentation de l’architecture
- Préparatifs
- Exercice 1 : afficher la liste des films
- Exercice 2 : rechercher des films
- Exercice 3 : récupérer les films de la bibliothèque
- Exercice 4 : marquer un film comme vu / non vu
- Exercice 5 : ajouter une note à un film
Présentation de l’architecture
Nous utiliserons pour ce TP une architecture client / serveur avec une communication via une API REST.
Une API (Application Programming Interface, interface de programmation applicative en français) est un ensemble de définitions et de protocoles permettant à un programme d’interagir avec un autre programme, en cachant les détails de l’implémentation.
Il s’agit donc d’une interface offerte par un programme informatique, une bibliothèque logicielle, un service ou encore un système d’exploitation.
Par exemple, un navigateur web expose des APIs JavaScript permettant de manipuler le DOM, de faire des requêtes HTTP, de gérer les cookies, etc.
Ainsi, la fonction getElementById est définie par l’API JavaScript du navigateur.
Dans une API REST, l’interface consiste en un ensemble d’URLs et de verbes HTTP (GET, POST, PUT, DELETE) permettant d’interagir avec le serveur. En retour, le serveur renvoie des données au format JSON.
Plus précisément, dans une API REST, chaque URL correspond à une ressource (un objet manipulé par l’API). Les verbes HTTP permettent au client de préciser l’action à effectuer sur la ressource.
Par exemple, pour une ressource movie, on pourrait avoir les routes suivantes :
GET /movies: récupérer la liste des filmsGET /movies/1: récupérer le film avec l’identifiant 1POST /movies: ajouter un nouveau filmPUT /movies/1: modifier le film avec l’identifiant 1
En retour, le serveur renvoie des données au format JSON, avec un code HTTP indiquant si la requête a réussi ou non. Par exemple, pour la requête GET /movies/1, le serveur pourrait renvoyer :
{
"id": 1,
"title": "The Matrix",
"year": 1999,
"summary": "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."
} Ou encore, si le film n’existe pas, le serveur renverra un code HTTP 404 (Not Found).
Les APIs REST sont donc construites avec les briques de bases du protocole HTTP (URLs, verbes HTTP, codes HTTP).
Le JSON (JavaScript Object Notation) est un format de données qui permet de représenter des objets sous forme de chaînes de caractères. Il possède une syntaxe similaire à celle des objets JavaScript, à ceci près que les clés doivent être entourées de guillemets doubles.
Ainsi, dans ce type d’architecture, contrairement à une architecture MVC, le serveur ne s’occupe pas de générer les vues (HTML).
Il ne transmet que des données au format JSON. C’est le client (navigateur) qui se charge de générer les vues pour les afficher à l’utilisateur.
Préparatifs : installer et lancer le projet
Nous utiliserons l’environnement de développement sur les machines de l’école.
Clonage du projet
Le code se trouve sur Github. Pour installer le projet, lancer les commandes suivantes dans un terminal:
# Clone le projet git dans le dossier courant
git clone https://github.com/johangirod/TP-REST
cd TP-REST
npm install Installer les extensions vscode
Ouvrez l’éditeur vscode avec la commande suivante :
code . & Ouvrez le menu des extensions (Ctrl+Maj+X) et cherchez “@recommended”. Vous devriez voir apparaître les extensions suivantes :
- Thunder Client
- ESLint
- Prettier - Code formatter
Installer ces trois extensions.
Présentation du projet
Le code source du serveur se trouve dans le dossier server, celui du javascript executé sur le navigateur se trouve dans le dossier public, dans le fichier app.js.
Lancer le projet
Pour lancer le serveur, lancer la commande suivante :
npm run dev Vérifiez que le serveur est bien lancé en allant sur http://localhost:3000.
Note il est conseillé de laisser les logs toujours ouverts quand vous développez, vous verrez directement les messages d’erreur s’il y en a.
Exercice 1 : afficher la liste des films
Le but de cet exercice est d’afficher la liste des films sur la page d’accueil.
Créer une route sur le serveur
Pour cela, nous allons créer une nouvelle route /movies sur le serveur qui renverra la liste des films au format JSON.
Dans le fichier server/routes/movies.ts, créer une route GET /movies qui renvoie la liste des films au format JSON.
Pour manipuler les films, vous utiliserez les fonctions exportées par le fichier
server/models/movies.ts.Pour envoyer une réponse au format json, vous pouvez utiliser la fonction
res.json()de express.
Testez votre route avec Thunder Client
Pour tester votre route, vous pouvez utiliser l’extension Thunder Client directement dans vscode.
Ouvrez Thunder Client avec la commande
Ctrl+Maj+Ppuis en tapant “Thunder Client: New request”.Dans la fenêtre qui s’ouvre, sélectionnez “GET” dans le menu déroulant, puis entrez l’URL de votre route :
http://localhost:3000/movies.Cliquez sur le bouton “Send” pour envoyer la requête.
Vous devriez voir apparaître la liste des films dans la réponse du serveur.
Afficher la liste des films sur la page d’accueil
Maintenant que nous avons une route qui renvoie la liste des films, nous allons afficher cette liste sur la page d’accueil, en modifiant le fichier public/app.js.
Pour récuperer les données du serveur, nous allons utiliser la fonction fetch en JavaScript.
Pour afficher les films, vous pourrez utiliser la fonction createMovieCard du fichier client/movie-card.js.
Cette fonction prend en paramètre un objet contenant la propriété movie du même type que celui retourné par la route /movies. Elle crée un élément HTML correspondant au film à afficher (image, titre, etc), et le retourne. Vous n’aurez plus qu’à l’insérer dans le DOM.
- Créer une la fonction
async function getMoviesqui retourne la liste des films récupérée depuis le serveur avec la fonctionfetch.const response = await fetch(`/movies`); const movies = await response.json(); - Créer une fonction
renderMoviesqui prend en paramètre la liste des films et qui affiche les films sur la page d’accueil en utilisant la fonctioncreateMovieCard.- Récupérer le container HTML qui contiendra les films avec la fonction
document.querySelector. - Supprimer son contenu avec la propriété
innerHTML. - Parcourir la liste des films et pour chacun d’entre eux, créer un élément HTML avec la fonction
createMovieCardet l’ajouter au container avec la fonctionappendChild.
- Récupérer le container HTML qui contiendra les films avec la fonction
- Dans la fonction
main, appeler la fonctiongetMoviespour récupérer la liste des films, puis appeler la fonctionrenderMoviespour afficher les films sur la page d’accueil.
Exercice 2 : rechercher des films
Nous allons maintenant ajouter la possibilité de rechercher des films par titre grâce au champs de recherche.
Modifier la route
/moviescôté serveur pour qu’elle prenne en paramètre un querystringqueryet le passe à la fonctiongetMovies. Vous pouvez tester votre route avec l’extension Thunder Client.Modifier la fonction
getMoviescôté client pour qu’elle prenne en paramètre une chaîne de caractèrequeryet qu’elle l’envoie au serveur dans la requêtefetch.Brancher le champs de recherche sur la fonction
getMoviespour qu’elle soit appelée à chaque fois que l’utilisateur tape une lettre dans le champs de recherche.- Dans la fonction
main, récuperer l’élement HTML du champs de recherche avec la fonctiondocument.querySelector([data-search-input]). - Écoutez les événements
inputsur le champs de recherche avec la fonctionaddEventListener. Cet événement est déclenché à chaque fois que l’utilisateur tape une lettre dans le champs de recherche. - A chaque fois que l’événement est déclenché, appeler la fonction
getMoviesavec la valeur du champs de recherche en paramètre pour récupérer la liste des films correspondant à la recherche, puis appeler la fonctionrenderMoviespour actualiser l’affichage.
- Dans la fonction
Vérifier que la recherche fonctionne en testant votre application
Exercice 3 : récupérer les films de la bibliothèque
Dans cet exercice, nous allons différencier les films qui sont déjà dans la bibliothèque de l’utilisateur de ceux qui ne le sont pas. Nous allons également afficher la note que l’utilisateur a donné à chaque film.
Côté serveur
Nous allons créer une ressource library qui contenant les films de la bibliothèque de l’utilisateur et la note donnée. Son type est le suivant :
type LibraryMovie = {
movieId: string; // l'identifiant du film
rating?: number; // la note donnée par l'utilisateur (optionnelle)
}; Pour exposer cette ressource avec l’API, nous allons créer une route GET /library/:movieId qui renvoie la ressource si le film avec l’identifiant movieId est dans la bibliothèque de l’utilisateur, et une erreur 404 sinon.
- Créer une nouvelle route
GETdans le fichierserver/routes/library.ts. - Récupérer l’identifiant du film dans les paramètres de la route avec
req.params.movieId. - Récupérer le film dans la bibliothèque de l’utilisateur avec la fonction
getLibraryMovieByIddu fichierserver/models/library.ts. Cette fonction renvoie une erreurNotFoundErrorsi le film n’est pas dans la bibliothèque de l’utilisateur. - Renvoyer les informations du film de la bibliothèque si il est présent. Sinon retourner une erreur 404 avec la fonction
res.status(404).send(). - Tester votre route avec l’extension Thunder Client.
Côté client
Pour chaque objet movie affiché, nous allons faire un appel à la route /library/:movieId pour vérifier si le film est dans la bibliothéque. Puis, il faudra modifier l’objet movie pour ajouter deux nouvelles propriétés :
isInLibrary: un booléen indiquant si le film est dans la bibliothèque de l’utilisateurrating: la note donnée par l’utilisateur au film (si elle existe)
Créer une fonction
aync function getLibraryMovie(movieId)qui vérifie si le film est dans la bibliothèque de l’utilisateur en appelant la routeGET /library/:movieIddu serveur. Cette fonction renvoiefalsesi le film n’est pas dans la bibliothèque de l’utilisateur (le seveur renvoie 404), et l’objet renvoyé par le serveur si il est présent (contenant les propriétésratingetmovieId).Modifier la fonction
async function getMoviespour ajouter les propriété de la bibliothèque à chaque film de la liste avant de la retourner. Pour modifier la liste, vous pouvez utiliser au choix :- une boucle for :
for (const movie of movies) { ... }(méthode impérative) - la fonction
map:movies.map(movie => { ... })etPromise.allpour attendre la fin de toutes les requêtes (méthode fonctionnelle)
- une boucle for :
Vérifier que cela fonctionne en rafrachissant l’application. Vous devriez voir apparaître des notes sur certains films.
Exercice 4 : marquer un film comme vu ou non vu
Maintenant nous allons permettre à l’utilisateur de modifier la liste des films de sa bibliothèque, en ajoutant ou en supprimant des films.
Cela, grâce à deux nouvelles routes à la ressource library qui vont faire leur apparition :
POST /librarypour ajouter un film à la bibliothèque de l’utilisateurDELETE /library/:movieIdpour supprimer un film de la bibliothèque de l’utilisateur
Côté serveur
Créer une route
POST /librarydans le fichierserver/routes/library.ts- Cette route paramètre un objet
LibraryMovieprésent dans le corps de la requête - Elle appelle la fonction
addMovieToLibrarydu fichierserver/models/library.tspour ajouter le film à la bibliothèque de l’utilisateur. Cette fonction renvoie une erreurBadRequestErrorsi le film est déjà dans la bibliothèque de l’utilisateur. - Si le film est déjà dans la bibliothèque de l’utilisateur, la route renvoie un code d’erreur 400 (Bad Request) avec le message d’erreur. Sinon, elle retourne l’objet ajouté.
- Tester votre route avec l’extension Thunder Client.
- Cette route paramètre un objet
Créer une route
DELETE /library/:movieIddans le fichierserver/routes/library.tsqui renvoie une 404 si le film n’est pas dans la bibliothèque. Tester votre route avec l’extension Thunder Client.
Coté client
Ajout d’un film à la bibliothèque
Créer une fonction async function addMovieToLibrary(movieId) qui appelle la route POST /library avec dans le corps de la requête l’identifiant du film à ajouter au format JSON. Ces informations seront spécifiées dans le second paramètre de la fonction fetch.
method: la méthode HTTP à utiliser (iciPOST)headers: les headers de la requête (ici{ "Content-Type": "application/json" " })body: le corps de la requête (l’identifiant du film au format JSON)
Passer cette fonction en tant que callback dans les arguments de la fonction createMovieCard : createMovieCard({ movie, onAddToLibrary: addMovieToLibrary }).
Le callback onAddToLibrary est appelé lorsque l’utilisateur clique sur le bouton “Ajouter”. Il prend en paramètre l’identifiant du film à ajouter.
Suppression d’un film de la bibliothèque
Créer une fonction async function removeFromLibrary(movieId) qui appelle la route DELETE /library/:movieId avec dans le corps de la requête l’identifiant du film à supprimer au format JSON. De même, reliez cette fonction au bouton “Enlever” avec le paramètre callback onRemoveFromLibrary de la fonction createMovieCard.
Testez votre application en ajoutant et en supprimant des films de la bibliothèque et en rafraichissant la page pour vérifier que les modifications sont bien sauvegardées.
Exercice 5 : ajouter une note à un film
Dernière étape, nous allons permettre à l’utilisateur de modifier les notes des films de sa bibliothèque.
Pour modifier une ressource, c’est le verbe HTTP PUT qui est utilisé.
Côté serveur
Créez une route PUT /library/:movieId dans le fichier server/routes/library.ts qui prend en paramètre l’identifiant du film et la nouvelle note à lui attribuer dans le corps de la requête. Vous utiliserez la fonction modifyLibraryMovie du fichier server/models/library.ts pour modifier la note du film. Cette fonction renvoie une erreur NotFoundError si le film n’est pas dans la bibliothèque de l’utilisateur.
Tester votre route avec l’extension Thunder Client.
Côté client
Utiliser le callback updateRating passé en paramètre de la fonction createMovieCard. Ce callback est appelé lorsque l’utilisateur modifie la note d’un film. Il prend en premier paramètre l’identifiant du film et en second paramètre la note donnée.
Bonus
- Séparer les films de la bibliothèque de l’utilisateur dans une section à part sur la page d’accueil
Questions de fin
- Quels sont les avantages d’une API REST par rapport à une architecture MVC ?
- Quels sont les inconvénients d’une API REST par rapport à une architecture MVC ?
- Nous souhaitons ajouter la possibilité à l’administrateur de l’application d’ajouter des films à la base de donnée. Quelle route devons-nous créer ? Quel verbe HTTP utiliser ?
- Et pour modifier un film présent ?