f) Introduction aux applications backend en Nodes.js & Express
Si vous n'avez jamais développé d'application backend en TS, nous vous recommandons de réaliser cette partie du cours attentivement.
C'est quoi une application backend ?
Frontend, backend, c'est quoi ?
Un frontend, c'est une application :
- qui s'exécute côté client, une IHM (Interface Homme-Machine), qu'on pourrait aussi appeler UI (User Interface) ;
- qui est en direct interaction avec l'utilisateur ;
- implémenté, dans ce cours-ci, en HTML / CSS / TS.
Un backend, c'est une application :
- qui s'exécute côté serveur ;
- qui n'offre pas d'interaction directe avec l'utilisateur ;
- qui parfois met à disposition le frontend ;
- qui parfois offre des opérations sur des données ;
- implémenté dans ce cours-ci en Node.js.
Rôles principaux du backend
Fourniture du frontend
Un des rôles du backend est de fournir le frontend :
- via un serveur de fichiers statiques ; c'est la mise à disposition des assets : fichiers HTML, CSS, JS, images...
- via la génération dynamique de pages HTML ; c'est ce qui se passe quand le backend fait du Server Side Rendering (SSR), généralement dans le cadre de Multi Page Applications.
Dans le cadre de ce cours, nous ne ferons pas de génération dynamique d'HTML côté serveur (ou SSR).
Fourniture d'opérations sur des ressources
Un autre rôle important du backend est de mettre à disposition des opérations sur des ressources, c'est ce qu'on appelle les services web ou web API.
Il existe différents types de technologies et architectures web permettant d'implémenter des web services, notamment :
- RESTful API ; c'est l'architecture qui actuellement est la plus utilisée et qui sera apprise dans le cadre de ce cours.
- GraphQL API ; c'est une technologie récente qui permet de très rapidement créer des requêtes sur des ressources et qui a été créée par Facebook ; nous ne verrons pas cette technologie dans le cadre de ce cours.
- SOAP API ; c'est une façon ancienne de créer des opérations sur des ressources mettant en oeuvre de l'XML pour communiquer entre des applications clients / serveurs. Nous ne verrons pas cette technologie dans le cadre de ce cours.
Autres rôles du backend ?
Un backend peut offrir d'autres services, comme :
- proxy : intermédiaire entre les clients demandant une ressource et le serveur fournissant cette ressource. On verra ce type de service, notamment pour masquer l'origine d'une requête à une API.
- reverse proxy : c'est un serveur qui fait l'intermédiaire avec d'autres serveurs, cachant au client qui est le véritable serveur ayant traité sa requête. Par exemple, un proxy serveur peut mettre à disposition des accès à des serveurs interne à une entreprise (non visibles sur le web) alors que le client interroge un serveur qui est visible sur le web.
- serveur d'emails ;
- ...
Node.js c'est quoi ?
Node.js est un environnement serveur open source permettant la création d'outils et applications côté serveur en JS.
Node.js offre une utilisation optimale des ressources des serveurs sans dépendance à un serveur http externe, tout en étant multiplateforme (Windows, Linux, Mac…).
Pour le développement d'IHM de façon modernes, vous avez déjà installé l'environnement Node.js. Mais si ce n'est pas installé, il est important que vous installiez l'environnement Node.js en version LTS [R.34].
💭 Mais je souhaite développer des applications en TS. Comment vais-je utiliser l'environnement Node.js pour faire du TS ?
Lors de notre développement, nous allons utiliser un outil permettant de transpiler du TS en JS, c'est à dire de transformer du TS en JS.
Où mettre du code TS dans une application Node.js ?
Directement dans un terminal
Il est possible d'écrire du code Node.js directement dans un terminal.
Pour rappel, nous vous conseillons d'utiliser Git Bash comme terminal au sein de VS Code.
Vous devriez déjà avoir configuré VS Code pour avoir comme Terminal par défaut Git Bash. Si ce n'est pas fait, nous vous rappelons la procédure :
- Vous devez avoir installé Git sur votre machine.
- Cliquez à droite du + au sein d'un terminal ouvert dans VS Code, clic sur Select Default Profile, puis sélectionnez "Git Bash". Tous les prochains terminaux que vous ouvrirez le seront sous Git Bash, nettement plus coloré et intéressant que les autres terminaux 😎.

Veuillez tester du code Node.js directement dans un terminal en tapant cela au sein d'un terminal de VS Code :
bashnode
Vous avez maintenant accès au terminal de Node.js.
Vous pouvez tenter une opération mathématique de votre choix, comme par exemple : 2 * Math.PI
Quand vous souhaitez sortir de l'interpréteur de commandes de Nodes, il faut taper :
- soit deux fois
CTRL c
; - soit
CTRL d
.
Dans un script
Nous écrivons généralement le code Node.js au sein d'un script externe.
Nous allons utiliser ts-node
pour exécuter directement des fichiers TypeScript sans les compiler manuellement en JavaScript au préalable.
ts-node
:
- utilise le compilateur TypeScript pour transpiler le code TypeScript en JavaScript.
- une fois le code transpilé,
ts-node
utilise Node.js pour exécuter le JavaScript résultant.
Pour lancer un script en TS à l'aide de ts-node
(et de Node.js indirectement), il suffit de taper dans un terminal : npx ts-node nomScript
.
npx
est un outil de Node.js qui permet d'exécuter des paquets npm sans les installer globalement, en les installant temporairement si nécessaire, simplifiant ainsi l'exécution de scripts et commandes de projets.
Dans votre repo web2, veuillez créer un répertoire /tutorials/ts-node-start
et y ajouter le fichier index.ts contenant ce code-ci :
tsconst dacia= {brand: "Dacia",model: "Sandero",id: undefined,getDescription: function () {return `${this.id} ${this.brand} ${this.model}`;},};console.log(dacia.getDescription());dacia.id = Math.random();console.log(dacia.getDescription());
Pour lancer le script créé ci-dessus, veuillez ouvrir un terminal au bon endroit.
Pour rappel, Il est possible de faire un clic droit dans l'Explorer de VS Code sur le répertoire ts-node-start
, Open in Integrated Terminal
pour ouvrir un terminal à l'endroit souhaité.
Il ne vous reste plus qu'à taper :
bashnpx ts-node ./index # or node ./index.ts
Nous avons une erreur à l'exécution du script. En effet, l'inférence automatique de type de TS a défini id
comme étant de type undefined
, on ne peut donc lui réassigner un Number
.
Il est intéressant de se rendre compte qu'en JS, on peut réassigner un type à une variable, ce qui n'est pas possible en TS. N'hésitez donc pas à exécuter ce script à l'aide de node ./index.ts
pour voir le résultat.
Revenons au TS. Nous allons donc définir un type Car
pour notre objet dacia
, en indiquant que l'attribut id
est de type number | undefined
(soit un nombre, soit undefined
). Voici le code modifié :
tsinterface Car {brand: string;model: string;id: number | undefined;getDescription: () => string;}const dacia: Car = {brand: "Dacia",model: "Sandero",id: undefined,getDescription: function () {return `${this.id} ${this.brand} ${this.model}`;},};console.log(dacia.getDescription());dacia.id = Math.random();console.log(dacia.getDescription());
Exécutez le script à l'aide de npx ts-node ./index
pour voir le résultat.
Si votre application ne s'exécute pas correctement et affiche un message du type : To load an ES module, set "type": "module" in the package.json
, c'est que votre transpilateur TS n'a pas correctement transpilé le code en JS. Il tente de générer un module ES6, au lieu de générer du code CommonJS (format d'une application Node.js). Pour résoudre ce problème, veuillez ajouter un fichier tsconfig.json
à la racine de votre projet contenant :
json{"compilerOptions": {"target": "ES6","outDir": "./build/","module": "commonjs","strict": true,"noUnusedLocals": true,"noUnusedParameters": true,"noImplicitReturns": true,"noFallthroughCasesInSwitch": true,"esModuleInterop": true,"resolveJsonModule": true}}
Voilà, c'est une simple application Node.js en TS qui affiche deux messages dans le terminal.
Introduction aux packages Node.js & npm
Introduction
Les concepts importants du gestionnaire de packages de Node.js sont résumés ci-dessous.
Gestionnaire de packages
npm est le gestionnaire de packages de Node.js.
On peut faire des recherches de packages qui seraient utiles à nos application web sur npmjs.com [R.48].
Fichier de configuration d'un projet
Tous les packages associés à une app, ses dépendances, sont données dans le fichier : package.json
.
C'est le fichier qui décrit la configuration d'un projet JS.
On peut manuellement créer ce fichier à l'aide de la commande npm init
si l'on souhaite quelque chose de plus interactif ou npm init -y
si l'on souhaite un fichier avec le minimum autogénéré.
Voici un exemple de fichier package.json
:
json{"name": "api2","version": "0.0.0","private": true,"scripts": {"debug": "npm run dev","dev": "nodemon ./bin/www","start": "node ./bin/www","lint": "eslint **/*.js","lint:fix": "npm run lint -- --fix"},"nodemonConfig": {"ignore": ["data/*"],"exec": "npm run lint && node"},"dependencies": {"cookie-parser": "~1.4.4","debug": "~2.6.9","express": "~4.16.1","morgan": "~1.9.1"},"devDependencies": {"eslint": "^8.19.0","eslint-config-airbnb-base": "^15.0.0","nodemon": "^2.0.19","prettier-airbnb-config": "^1.0.0"},"author": "e-Baron"}
Lors de l'installation d'un package, celui-ci s'ajoute à la liste des dépendances.
Ainsi, si des développeurs trouvent votre projet sur Git, ils n'auront qu'à exécuter npm i
afin d'installer toutes les dépendances.
C'est dans le répertoire node_modules
que toutes les dépendances seront installées.
Ces dépendances peuvent être très volumineuses. C'est donc important de ne jamais mettre ce dossier sur vos web repository, via Git.
Pour ce faire, n'oubliez pas d'inclure un fichier .gitignore
dans vos repos pour ignore node_modules
.
package.json
indique les scripts de démarrage, en fonction de la façon dont nous souhaitons démarrer l'application.
json"scripts": {"debug": "npm run dev","dev": "nodemon ./bin/www","start": "node ./bin/www","lint": "eslint **/*.js","lint:fix": "npm run lint -- --fix"},
Au regard de cette configuration, on peut démarrer l'application à l'aide de npm start
ou npm run start
, ce qui exécutera le script ./bin/www
à l'aide de node.
On pourrait aussi démarrer l'application à l'aide de npm run dev
, ce qui démarrerait l'application à l'aide de nodemon
, un outil permettant de monitorer les changements de fichiers et de redémarrer automatiquement le serveur en cas de changement.
Installer un package
Généralités
Pour installer un package (ou une dépendance), il suffit de faire : npm i nomDuPackage
ou npm install nomDuPackage
.
Pour installer une dépendance de développement, qui ne sera pas installée lorsque nous déploierons une version de production de notre application, tapez : npm i nomDuPackage -D
.
Par exemple, nodemon
est un package qui permet de redémarrer le serveur à chaque modification de fichier par les développeurs. En production, l'application n'a pas besoin de ce package !
Gestion des types lors de l'utilisation de TS
Attention, l'installation des types via npm i @types/nomDuPackage -D
est souvent essentielle pour utiliser des packages JavaScript dans un projet TypeScript.
Cette installation fournit les définitions de types nécessaires pour que TypeScript comprenne la structure et les types des objets, fonctions, et classes dans le package. Cela permet à l'éditeur de code d'offrir l'auto-complétion, la vérification de type, et des messages d'erreur précis lors du développement. Ces types sont installés en tant que dépendances de développement (-D
), car ils ne sont nécessaires que pour le développement, pas pour l'exécution en production.
Par exemple pour utiliser uuid
dans un projet TS (https://www.npmjs.com/package/uuid), il est important d'installer les définitions de type pour ce package. Pour installer ce package :
shnpm i uuidnpm i @types/uuid -D
Certains packages, souvent ceux écrits directement en TypeScript, incluent déjà leurs propres définitions de types dans leur code source. Dans ce cas, vous n'avez pas besoin d'installer @types/nomDuPackage
séparément.
Si vous ne savez pas si vous devez installer les définitions de types pour un package, vous pouvez toujours essayer de l'utiliser sans les définitions de types. Si vous obtenez des erreurs de compilation TypeScript, vous devrez installer les définitions de types, et la façon de le faire sera indiqué dans les messages d'erreur (via Quick Fix...
).
Dépendances installées
On a vu que npm i
permet d'installer toutes les dépendances se trouvant dans package.json
, ainsi que toutes les dépendances de ces dépendances...
L'arbre exact des dépendances installées, numéros de version..., se trouve dans package-lock.json
. Ce fichier est généré automatiquement pour chaque opération modifiant node_modules
ou package.json
.
Le fichier package-lock.json
est utilisé pour verrouiller les versions des dépendances dans un projet Node.js, garantissant que les installations futures produisent exactement le même arbre de dépendances, ce qui assure la consistance et la reproductibilité des builds.
⚡ Si un fichier package-lock.json
est compris dans un repo, lorsque vous introduirez npm i
pour installer toutes les dépendances, npm installera les mêmes versions que celles se trouvant dans package-lock.json
. Cela peut poser des problèmes si votre environnement Node.js est en version différente. En cas de souci, pensez à effacer le répertoire node_modules
et le fichier package-lock.json
avant de relancer l'installation de toutes les dépendances.
Localisation d'un module ou package par Node
Node va chercher dans tous les chemins spécifiés dans la variable module.paths
lorsque required()
est appelé. Les chemins cherchés sont par exemple : node_modules
, ./, ...
Mise à jour des packages vers leur dernière version
La mise à jour de toutes les dépendances peut parfois amener à des gros soucis.
Sans prendre trop de risques, vous pouvez tenter de mettre à jour tous vos packages en suivant la documentation de npm : Updating packages downloaded from the registry [R.49].
Cela vous permettra de mettre à jour vos packages, sans devoir changer la liste de vos dépendances qui est donnée dans package.json
. S'il y a des dépendances qui ont des "breaking changes", ces versions ne seront pas installées.
Si vous souhaitez tous mettre à jour d'un coup, vers la toute dernière version de chaque package, vous pouvez tentez le coup en utilisant la commande ncu
du package npm-check-updates.
Mais attention, certains packages subissent parfois des "breaking changes", ce qui impose que vous deviez faire migrer votre code avant que celui-ci soit fonctionnel.
Nous vous recommandons donc, si vous rencontrez un problème lors de la mise à jour de tous vos packages d'un coup à l'aide de ncu
, de revenir à la situation initiale, et de faire l'upgrade de chaque package listé dans package.json
individuellement. Pour installer la dernière version d'un package, utilisez @latest
:
bashnpm i nomPackage@latest
Express c'est quoi ?
Voici le moto du framework Express: "Fast, unopinionated, minimalist web framework for Node.js" Express [R.50].
Express est un framework qui permet de rapidement créer des applications en Node.js.
Il est possible de créer une application Express soit "from scratch", soit à partir d'un boilerplate.
Dans le cadre de ce cours, nous allons plutôt utiliser des boilerplates pour générer des applications. Néanmoins, il est intéressant de voir comment créer une application "from scratch".
Créer une application Express "from scratch"
Il faut d'abord créer un répertoire pour votre application.
Au sein de votre repo web2, veuillez créer le répertoire /tutorials/back/express-static-file-server
.
Veuillez, via le terminal, entrer dans ce répertoire. Vous pouvez par exemple faire un clic droit sur sur le répertoire /tutorials/back/express-static-file-server
dans l'Explorer de VS Code, Open in Integrated Terminal
.
Dans ce répertoire, veuillez générer le fichier de configuration du projet (package.json
) : npm init
.
Veuillez répondre aux questions pour configurer le point d'entrée de l'application comme étant le script index.ts
.
Veuillez configurer le script de démarrage de votre application en ajoutant cette ligne au sein de package.json
:
json"scripts": {"start": "ts-node index.ts",//...}
Ainsi, notre application pourra démarrer à l'aide de la commande npm start
.
Néanmoins, pour que cela fonctionne, il faut installer ts-node
.
Veuillez installer ts-node
, comme dépendance de développement, à l'aide de la commande suivante :
bashnpm i ts-node -D
Il est à noter que nous aurions pu installer ts-node
de façon globale (npm i ts-node -g
), mais nous préférons l'installer localement pour que les autres développeurs qui travailleront sur ce projet puissent avoir la même version de ts-node
.
Il aurait aussi été possible de créer une installation temporaire de ts-node
à l'aide de npx
: npx ts-node index.ts
.
A ce stade-ci, voilà à quoi devrait ressembler votre package.json
:
json{"name": "express-static-file-server","version": "1.0.0","description": "","main": "index.ts","scripts": {"start": "ts-node index.ts","test": "echo \"Error: no test specified\" && exit 1"},"author": "yourName","license": "ISC","devDependencies": {"ts-node": "^10.9.2"}}
Veuillez installer le package express
:
bashnpm i express
Nous allons créer un simple serveur de fichiers statiques à l'aide du middleware express.static
, afin de servir tous les fichiers qui se trouveront dans le répertoire public
.
Tout d'abord, téléchargez ce zip : fichiers statiques.
Veuillez désarchiver ce répertoire dans votre projet afin d'avoir les fichiers statiques qui seront partagés par votre serveur au sein de /public
.
Vérifiez bien que vous n'avez qu'un seul répertoire /public
et pas un /public/public
.
Nous allons créer le serveur via un nouveau fichier /index.ts
et y ajouter ce code :
tsimport express from "express";const app = express();app.use(express.static("public"));const PORT: number = 7777;app.listen(PORT, () => {console.log(`Server running on port ${PORT}`);});
Veuillez tenter de lancer l'application en tapant cela dans votre terminal au sein du répertoire du projet :
bashnpm start
Vous rencontrerez l'erreur "Could not find a declaration file for module 'express'"
parce que TypeScript ne peut pas trouver les définitions de type nécessaires pour Express. Cela empêche TypeScript de connaître les types et les signatures des fonctions d'Express, ce qui invalide les bénéfices du typage statique et de l'autocomplétion dans l'IDE. Ainsi, la compilation échoue.
Pour résoudre ce problème, nous devons simplement installer les définitions de type pour Express :
bashnpm i @types/express -D
Veuillez lancer l'application en tapant cela dans votre terminal au sein du répertoire du projet :
bashnpm start
Pour accéder au serveur de fichiers, vous pouvez le faire via un browser : http://localhost:7777
Si tout se passe bien, vous devriez avoir accès au site de la pizzeria que nous découvrirons plus tard dans ce cours.
On voit que juste cette ligne permet la mise en place d'un serveur de fichier statique, via le middleware express.static
:
jsapp.use(express.static('public'));
Et pour démarrer un serveur web qui écoute sur le port 7777, ces lignes sont suffisantes :
jsapp.listen(PORT, () => {console.log(`Server running on port ${PORT}`);});
En cas de souci, vous pouvez accéder au code de ce tutoriel ici : express-static-file-server.
Créer une application Express via un générateur
Le générateur d'applications express-generator
permet de générer le boilerplate d'une application Express de base.
Malheureusement, ce générateur ne permet pas de générer une application en TypeScript. Nous avons donc choisi pour ce cours de créer pour vous un boilerplate de base d'une application Express en TypeScript et de ne pas utiliser le générateur express-generator
.
Si vous le souhaitez, vous pouvez passer à la Partie 1 : Création de services web.
🍬 Les modules CommonJS
Introduction
Un module est une librairie TS ou JS fournissant des objets.
Comme en TS/JS tout est objet, un module met donc à disposition des fonctions, des constantes, des variables...
Très souvent, Node.js est codé en JS conforme au standard CommonJS
. La majorité des exemples que vous trouverez sur le web pour des applications Express seront en CommonJS
.
C'est pourquoi, même si ce cours se veut plus moderne en utilisant du TS, nous vous proposons optionnellement de voir comment fonctionne les modules en CommonJS
dans ce chapître. Si vous préférez, vous pouvez passer directement à la section suivante : Introduction aux packages Node.js & npm.
Nous vous rappelons que pour ce cours-ci, nous utiliserons du TS et les modules ES6
comme vu dans l'introduction au modules; les modules en CommonJS
ne seront pas utilisés dans ce cours-ci.
Création d'un module
Pour créer un module, il suffit de créer un script JS nomModule.js et d'exporter des objets au sein de ce module via module.exports ou exports.
Exporter des objets
Introduction
Il existe plusieurs façons d'exporter des objets, soit à la volée, soit à la fin d'un script.
👍 Nous vous recommandons de faire vos exports à la fin du script, cela rend les scripts plus lisibles.
Nous allons néanmoins voir toutes les façons d'exporter des objets en CommonJS, car vous trouverez de tout sur le web.
On exporte toujours un seul objet principal dans un module. Cet objet peut bien sûr contenir une multitude d'objets via ses propriétés.
Export à la fin d'un script
C'est la façon la plus propre d'exporter un seul objet :
jsmodule.exports = router;
C'est l'équivalent d'un "default export" tel qu'il sera vu en ECMAScript.
S'il y a plusieurs objets à exporter, voici la façon recommandée de le faire :
jsmodule.exports = {authorize, users };
C'est l'équivalent d'un "Named export" tel qu'il sera vu en ECMAScript.
Export à la volée
Il est possible de faire des exports à la volée, c'est à dire d'exporter des objets au fur et à mesure qu'ils sont initialisés.
Voici la version longue :
jsmodule.exports.authorize = authorize;module.exports.users = users ;
Il est aussi possible d'écourter une export à la volée :
jsexports.authorize = authorize;exports.users = users ;
Voici quelques précisions :
module.exports
: c'est la référence de l'objet retournée par l'appel derequired()
(méthode qui sera utilisée pour l'import).exports
: c'est la référence versmodule.exports
,exports
n'est pas retourné par l'appel derequired()
.
⚡ Il faut faire attention au mauvais usage de l'utilisation de exports
.
Voici une mauvaise utilisation :
jsexports = { authorize, users }; /* exports has a new reference,it is no longer linked to module.exports */
Importer des objets
Introduction
Pour utiliser des objets (fonctions, constantes, objets, classes...) au sein d'un script JS provenant de modules, on le fait à l'aide de la fonction required()
et du chemin vers le module à utiliser.
Il est possible d'importer tant des objets de modules que l'on a créé soi-même, que de packages mis à disposition via un gestionnaire de packages.
Import d'un module
Lorsqu'un seul objet a été exporté, on l'importe en lui donnant le nom que l'on souhaite à l'import et en indiquant le chemin vers le module à utiliser.
jsconst pizzaRouter = require('./routes/pizzas');
Lorsque plusieurs objets ont été exportés, on importe ce que l'on souhaite en utilisant des accolades et en indiquant le chemin vers le module à utiliser.
jsconst { users, authorize } = require('../utils/auths');
Import d'un package
Il est aussi possible d'importer des objets de packages offerts par la communauté. Pour ce faire, il est juste nécessaire d'indiquer le nom du package lors de l'import.
Si un seul objet est exporté par un package, voici un exemple de comment le récupérer :
js// module integrated to the runtime environmentconst http = require('http');// module used after package installationconst shortid = require('shortid');