TP ExpressJS
Ce TP vous permettra de vous familiariser avec un framework web (expressjs
), en créant un site web pour un restaurant.
Ce site contiendra une page d’accueil, une page pour afficher les menus, et une page pour commander un menu.
Objectifs
- Avoir une première expérience avec l’architecture MVC
- Savoir créer un serveur web avec Express et créer des routes
- Savoir lancer des tests et interpréter les erreurs
- Servir les fichiers statiques
- Utiliser un moteur de template pour générer des pages HTML à partir de vues
- Comprendre l’utilité du
layout
- Comprendre et utiliser les query string parameters
- Savoir traiter un formulaire HTML
Sommaire
- Préparatifs
- Exercice 1 : utiliser le routeur et servir des fichiers statiques
- Exercice 2 : utiliser un moteur de template
- Exercice 3 : traiter un formulaire HTTP
- Exercice 4 : ajouter un middleware pour gérer les erreurs serveurs
Préparatifs : installer et lancer le projet
Prérequis : installation
Pour les besoins de développement, nous allons installer nodeJS en local.
Si cela ne fonctionne pas, nous utiliserons une version github codespace, un vscode en ligne avec nodeJS déjà installé.
Installation
Le code se trouve sur github. Pour installer le projet, lancer les commandes suivantes :
# Clone le projet git dans le dossier courant
git clone --branch start https://github.com/johangirod/TP-express
cd TP-express
npm install
Présentation du projet
Ce projet se compose d’un fichier index.ts
contenant le code source de votre serveur, et d’un dossier test
contenant les tests (vous n’aurez pas à modifier ce dernier).
Le fichier tsconfig.json
contient la configuration typescript.
Eslint
Ce projet utilise eslint pour vérifier la syntaxe du code. Pour lancer la vérification, lancer la commande npx eslint ./
.
Le fichier .eslintrc.js
contient la configuration d’eslint.
Prettier
Ce projet utilise prettier pour formater automatiquement le code. Pour lancer le formatage, lancer la commande npx prettier ./ --write
.
Le fichier .prettierrc.js
contient la configuration de prettier (vide = standard).
Installer les extensions vscode
Pour profiter des annotations eslint et du formattage automatique de prettier dans vscode, il vous suffit d’installer les extensions recommandées du projet. Pour cela, ouvrez le menu des extensions (Ctrl+Maj+X
) et cherchez “@recommended”. Vous devriez voir apparaître les extensions suivantes :
- ESLint
- Prettier - Code formatter
Lancer le projet
- Créez le fichier
index.ts
à la racine du projet, contenant un simpleconsole.log("hello world")
- Lancez le projet avec la commande
npx ts-node-dev index.ts
. Vous devriez voir apparaitre « hello world » dans la console.
ts-node-dev
est un outil qui permet d’exécuter un fichier node en typescript. Il est très pratique, car il relance automatiquement le serveur à chaque modification du code source.
Tests
Tout au long du TP, vous pourrez vérifier si vous avez réussi l’exercice en lançant les tests avec la commande npm test
.
Lancez la commande et vérifiez que tout est vert.
Exercice 1 : un serveur web static
Pour commencer, allez sur la branche exercice-1
du projet :
git checkout -f exercice-1
Le fichier index.html
contient la page d’accueil du site. Nous allons maintenant créer un serveur web qui servira cette page.
Ma première route
Nous allons créer une route /ping
qui renverra un statut 200.
Le statut HTTP est un code numérique qui est renvoyé par le serveur pour indiquer le résultat de la requête au client. Le code 200 signifie que la requête a été traitée avec succès. Le code 404 signifie que la ressource demandée n’a pas été trouvée. Nous verrons d’autres codes de statut HTTP plus tard dans ce TP.
Il vous faudra importer la bibliothèque express, créer une instance de l’application, et ajouter une route /ping
qui renvoie un statut 200.
- Installez express avec la commande
npm install express
- Installez les types typescript de express avec la commande
npm install -D @types/express
- Importez express dans le fichier
index.ts
avecimport express from 'express'
- Créez une instance de l’application express avec
const app = express()
- Ajoutez une route
/ping
qui renvoie un statut 200 avec la méthodeapp.get
- Faire en sorte que le serveur écoute sur le port 3000 avec la méthode
app.listen
- Lancez le serveur avec
npx ts-node-dev index.ts
- Lancez les tests avec
npm test
- Optionnel : ouvrez votre navigateur et allez sur
http://localhost:3000/ping
Servir la page d’accueil
Nous allons maintenant servir la page index.html
lorsque l’utilisateur se rend sur la page d’accueil du site. Pour cela, vous créerez une route /
qui renvoie le fichier index.html
.
Pour cela, vous utiliserez la méthode sendFile
de l’objet res
.
Pour construire le chemin du fichier, vous utiliserez la méthode join
du module path
. Et pour récupérer le chemin absolu du dossier racine, vous utiliserez la variable __dirname
.
Servir les fichiers statiques (images, css, etc.)
Tous les fichiers statiques (images, css, etc.) se trouvent dans le dossier assets
. Nous allons maintenant servir ces fichiers statiques, en montant le dossier assets
sur le chemin /assets
.
Pour cela vous utiliserez la méthode express.static
.
Tester le serveur
Vérifiez que tout fonctionne en lançant les tests avec npm test
avant de passer à l’exercice suivant.
Exercice 2 : Utiliser un moteur de template
Nous allons à présent utiliser un moteur de template pour générer les pages HTML à partir des données de l’application. Il en existe plusieurs, nous utiliserons handlebars.
Pour commencer, allez sur la branche exercice-2
du projet :
git checkout -f exercice-2
Un nouveau dossier models
a été créé, contenant un fichier restaurant.ts
qui contient les données du restaurant. Nous allons utiliser ces données pour générer les pages du site qui seront situées dans le dossier views
.
Un moteur de template est un outil qui permet de générer des pages HTML à partir de données. Il permet de séparer le code HTML du code JavaScript, et de générer des pages HTML dynamiques. C’est un outil utilisé dans les applications web qui suivent le pattern MVC.
Mettre en place handlebars
- Renommez le fichier
index.html
enhome.handlebars
et déplacez-le dans un nouveau dossierviews
- Installez le moteur de template handlebars pour express
express-handlebars
en suivant les instructions de la documentation - Vérifiez que les tests de l’exercice 1 passent toujours grâce à la commande
npm test exercice-1
. Si ce n’est pas le cas, corrigez le code.
Changer le nom et la description du restaurant
- Dans le fichier
home.handlebars
, remplacez le nom du restaurant par{{name}}
et la description par{{description}}
- Dans le fichier
index.ts
, importez le fichierrestaurant.ts
avecimport restaurant from "./models/restaurant"
- Passez les données du restaurant au moteur de template via
res.render
Utiliser les layouts
Un layout est un fichier qui contient le squelette de la page. Il permet de factoriser le code HTML commun à toutes les pages du site, comme le header et le footer. Nous allons mettre à jour le layout par défaut main.handlebars
, et laisser la possibilité de paramétrer le titre de la page.
- Déplacez le code HTML commun dans le fichier
main.handlebars
de telle sorte que le fichierhome.handlebars
ne contienne que le contenu spécifique à la page d’accueil (ce qui se trouve à l’intérieur de la balise<div class="row ...">
). - Changez le titre de la page d’accueil (balise
<title>
) par<nom du restaurant>
- Faire en sorte que les tests « Exercice 1 - Home » passent
Créer une page pour afficher le menu
Voici le template bootstrap proposé pour un menu :
<div class="col" data-test-menu>
<div class="card h-100" role="list-item">
<div class="card-body">
<h2 class="card-title">{{name}}</h5>
<p class="card-text">{{description}}</p>
<p class="card-text">{{price}}€</p>
</div>
</div>
</div>
Voici le container pour afficher les menus :
<div class="row row-cols-1 row-cols-md-3 g-4">
<!-- Ici, on affichera les menus -->
</div>
Créez une nouvelle page menu.handlebars
, servi sur l’URL /menus
qui affiche les menus du restaurant présents dans models/menus
. Pour éviter de dupliquer le code HTML, vous le bloc handlebars {{#each}}
pour afficher tous les menus. Cette page aura un titre <h1>
« Menus » et pour titre de page (balise <title>
) Menus - <nom du restaurant>
- Creer la vue
menu.handlebars
dans le dossierviews
- Servir cette page sur le route
/menus
grâce àapp.get
etres.render
- Passer la variable
menus
au moteur de template comme argument deres.render
- Utiliser
{{#each}}
dans la vue pour afficher tous les menus - Modifier le titre de la page pour qu’il soit dynamique
Menus - <nom du restaurant>
(<nom du restaurant>
vient du fichierrestaurant.ts
)
Créer une page pour commander un menu
Nous allons ajouter un bouton sur les menus pour pouvoir les commander. Ce bouton doit rediriger vers la page /commander
. Pour passer au serveur l’identifiant du menu, nous allons utiliser un paramètre de l’URL (appelé query string parameter
). Par exemple, pour le menu avec l’identifiant xid327y
, l’URL sera /commander?menu=xid327y
.
Voici le code du bouton :
<!-- contenu du menu -->
<!-- ... -->
<div class='p-3'>
<a href='/commander?menu={{id}}' class='btn btn-secondary'>Commander</a>
</div>
Un query string est une chaîne de caractères qui suit le chemin de l’URL et qui contient des paramètres. Il est séparé du chemin par un point d’interrogation ?
, et chaque paramètre est séparé par un &
. Par exemple, l’URL https://example.com/path?param1=value1¶m2=value2
contient deux paramètres : param1
et param2
.
Il est possible de récuperer les paramètres d’une requête HTTP avec la propriété query
de l’objet req
.
- Ajouter un bouton « Commander » sur chaque menu qui redirige vers la page
/commander
avec l’identifiant du menu en paramètre - Créer une page
commande.handlebars
qui le nom du menu commandé. Cette page aura un titre<h1>
« Votre commande : menu» et pour titre de page (balise <title>
)Commander - <nom du restaurant>
- Servez cette vue sur la route
/commander
grâce àapp.get
etres.render
- Pour récupérer l’
id
passé en paramètre de requête, vous pourrez utiliserreq.query
- Rechercher dans l’array
menu
celui qui correspond à l’id en entrée - Passer le nom du menu concerné à la vue, comme paramètre de
res.render
Aide : vous pourrez utiliser la méthode Array.find
pour retrouver le bon menu à partir de l’identifiant
Avant de passer à l’exercice suivant, vérifiez que les tests passent avec npm test
.
Exercice 3 : traiter un formulaire HTTP
Nous allons maintenant créer un formulaire pour commander un menu. Ce formulaire contiendra un champ pour le nom, un champ pour l’adresse, et un champ pour le numéro de téléphone. Lorsque l’utilisateur soumettra le formulaire, un message de confirmation s’affichera, lui rappelant ses informations saisies.
Créer le formulaire
Sur la page
commander.handlebars
, créez un formulaire avec la balise<form>
et des balises<input>
qui contient les champs suivants :- un champ
name
de typetext
avec le label « Nom » - un champ
address
de typetext
avec le label « Adresse » - un champ
phone
de typetel
avec le label « Téléphone » - un bouton « Commander » de type
submit
- un champ
Créez une route
/commander
enpost
qui extrait les données du formulaire et les fourni à la vuecommander.handlebars
. Vous utiliserezapp.post
. Pour récupérer automatiquement les données du formulaire, il faudra ajouter un middleware spécifique :app.use(express.urlencoded())
Vérifiez que la route est bien appelée lorsque vous soumettez le formulaire. Pour cela, vous pouvez ajouter un
console.log
et vérifier si il apparaît dans le terminal.Modifier la vue
commander.handlebars
pour ajouter un message de confirmation lorsque les données du formulaire sont présentes. Ce message doit contenir le nom, l’adresse et le téléphone du client.Veillez à ce que la page continue d’afficher le nom du menu commandé
Astuce : vous pourriez avoir besoin de <input type="hidden">
et de <form method="post">
Il existe plusieurs méthodes HTTP, permettant d’indiquer une intention au serveur. Les deux plus courantes sont GET
et POST
.
- La méthode
GET
est utilisée pour récupérer des données. Lorsqu’on saisit une URL dans le navigateur, ce dernier utilise la méthodeGET
. - La méthode
POST
est utilisée pour envoyer des données. Elle signifie que l’on souhaite effectuer une action qui va modifier un état sur le serveur.
Les données de la méthode POST
sont envoyées dans le corps de la requête HTTP, et non dans l’URL. Cela permet de transmettre des données plus importantes, comme des fichiers. Pour récupérer les données postées avec un formulaire, on utilise un middleware express appelé express.urlencoded()
qui les transforme en objet JavaScript et les ajoute à l’objet req.body
.
Exercice 4 : ajouter un middleware pour gérer les erreurs serveurs
Un middleware est une fonction de callback qui prend en paramètre un objet req
, un objet res
, et une fonction next
. Il est utilisé pour effectuer des opérations communes à plusieurs routes, comme la vérification des autorisations, la gestion des erreurs, ou le formatage des données.
app.use((req, res, next) => {
// Ce code est appelé avant chaque route handler (get, post, etc.)
console.log('Entrée dans le middleware');
// L'appelle à `next()` permet de passer la main au middleware suivant, ou au route handler si il n'y a plus de middleware
next();
// Ce code est appelé après chaque route.
// On peut accéder à la réponse de la route avec res
console.log('Sortie du middleware');
});
A noter : l’ordre des middleware est important. Ils sont exécutés dans l’ordre où ils sont déclarés. Par ailleurs, ils doivent être déclarés avant les routes qui les utilisent.
Gérer les erreurs 404
- Créer une vue
erreur.handlebars
qui affiche un message « Page non trouvée » lorsque le code erreur est 404. - Implémenter un middleware express qui affiche la vue avec les bons paramètres lorsqu’une page n’est pas trouvée
Gérer les erreurs 500
- Modifier la vue
erreur.handlebars
pour afficher le message « Erreur serveur » lorsque le code erreur est 500. - Implémenter un middleware express qui affiche la vue avec les bons paramètres lorsqu’une erreur se produit
Exercice 5 : améliorations (bonus)
Post/Redirect/Get
Après avoir saisi la commande, on reste sur la même url, ce qui signifie que si l’utilisateur rafraichit la page, le formulaire est renvoyé. Pour éviter cela, on peut utiliser le pattern Post/Redirect/Get : après avoir traité le formulaire, on redirige l’utilisateur vers une autre page. Il ne pourra donc pas renvoyer le formulaire en rafraichissant la page.
Utiliser un paramètre de l’URL
Plutôt que d’utiliser un query string pour passer l’identifiant du menu, on peut utiliser un paramètre de l’URL. Cela éviterait d’avoir à se servir de input hidden. Par exemple, l’URL /commander/1
permet de passer l’identifiant du menu 1
. Pour cela, il faut utiliser un paramètre de l’UgwsRL, et non un query string. On peut utiliser la méthode app.param
pour récupérer le menu à partir de l’identifiant, et le passer à la vue.
Refacto
Tout le code source est dans le fichier index.ts
. Il serait préférable de séparer le code en plusieurs fichiers, par exemple un fichier controllers.ts
qui contient les routes et laisser le fichier index.ts
pour la configuration du serveur.
Validation du formulaire
Lors de la soumission du formulaire, on pourrait vérifier que les champs sont bien remplis, et afficher un message d’erreur sur le champs concerné si ce n’est pas le cas. Par ailleurs, on pourrait vérifier que le numéro de téléphone est bien un numéro de téléphone valide.