Installation
L'installation d'Express se fait simplement grâce à npm:
npm install express
Nous avons vu que le runtime Node.js fournit les bases permettant de développer un serveur web en Javascript. Cependant, développer un site complet sur ces bases uniques serait très complexe : il faudrait écrire nous-mêmes de nombreuses fonctionnalités nécessaires comme le routage, l'extraction de données contenues dans les requêtes, la génération de pages basée sur des templates, etc.
Le framework Express est un package Node.js largement utilisé par les développeurs et qui facilite grandement l'ensemble de ces tâches.
L'installation d'Express se fait simplement grâce à npm:
npm install express
La mise en oeuvre d'Express commence par la définition d'une application basée sur ce framework : il s'agit de créer un script qui utilise Express pour gérer les requêtes envoyées au serveur.
Une approche consiste à créer dans backend/
un nouveau fichier nommée
app.js
:
app.js
const express = require('express'); // inclusion d'express
// Instanciation d'une application express
const app = express();
// Configuration de l'application : une première gestion "basique" des requêtes.
app.use((req, res) => {
// Une fois encore, les requêtes sont pour l'instant toutes traitées de la même manière.
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end('Le serveur Express dit <b>bonjour</b>');
});
// Exportation de notre application express
module.exports = app;
Il faut ensuite modifier notre point d'entrée server.js
pour qu'il délègue le traitement des
requêtes à l'instance créée.
server.js
require('dotenv').config({ path: './.env'} ) ;
const port = process.env.PORT || 3000
const http = require('http');
const app = require('./app'); // inclusion d'Express
// mise en oeuvre : on délègue la gestion des requêtes à Express
const server = http.createServer(app);
server.listen(port, ()=>{
console.log(`Le server écoute sur http://127.0.0.1:${port}/`);
})
Une autre solution est de mettre tout le code de démarrage du serveur dans le fichier d'application
app.js
(et donc de supprimer le fichier server.js
qui devient inutile).
Exemple :
app.js
require('dotenv').config({path: './.env'})
const express = require('express'); // inclusion d'express
const app = express(); // Instanciation d'une application express
// Configuration de l'application : une première gestion "basique" des requêtes.
app.use((req, res) => {
// Une fois encore, les requêtes sont pour l'instant toutes traitées de la même manière.
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end('Le serveur Express dit <b>bonjour</b>');
});
const port = process.env.PORT || 3000
app.listen(port,()=>{
console.log(`Le server écoute sur http://127.0.01:${port}/`);
})
Bien entendu, il faut dans ce cas mettre à jour les scripts de démarrage dans le fichier
package.json
pour ne plus utiliser server.js
mais directement app.js
.
L'application express que nous venons d'implémenter est très (trop) simple : toutes les requêtes sont à
nouveau traitées de la même manière : dans le handler passé à app.use
, nous n'avons fait
aucun usage du paramètre req
qui contient les informations spécifiques concernant la requête
reçue.
Dans une application "réelle", le serveur va généralement traiter les requêtes différemment selon les
informations qu'elles contiennent (chemin, méthode, etc.) : par exemple, une requête utilisant la
méthode GET
, ne sera certainement pas traitée comme une requête utilisant la méthode
POST
.
Dans Express, cette différenciation est réalisée sous la forme de middlewares.
Dans Node.js et Express, les informations relatives à la requête sont disponibles
dans l'objet req
qui est transmis aux handlers que vous implémentez.
Par exemple, dans le app.js
précédent, vous pouvez demander au serveur d'afficher la méthode
utilisée pour la requête reçue en ajoutant la ligne :
app.use((req, res) => {
console.log(req.method);
...
});
A chaque requête reçue (rafraichissez la page dans le navigateur), vous verrez apparaitre dans la console du serveur la méthode utilisée.
De fait, dans une application complexe, il faut écrire le code qui va extraire les informations
contenues dans les requêtes et décrire tous traitements spécifiques spécifiques à effectuer
pour chaque type de requête : par ex. le traitement des paramètres éventuels d'une requête GET
,
le parsing du corps d'une requête POST
et la récupération des données transmises
(données de formulaires, fichiers uploadés, ...), etc.
Écrire la totalité de ces instructions dans le seul handler de notre app.use
serait
(entre autres) illisible. De plus, une bonne partie de ces traitements est commune au fonctionnement
de tous les serveurs web, et de nombreuses fonctionnalités ont déjà été implémentées par la communauté des
développeurs.
Pour permettre le découpage et l'intégration des traitements, Express introduit la notion de
middleware : une application va faire passer les informations concernant la requête
(req
) et la construction de la réponse (res
) dans une chaine de middlewares
dont le dernier sera chargé d'envoyer la réponse finale au client.
Le app.use
que nous avons écrit est en fait lui-même un middleware de notre application.
Une manière d'ajouter des middlewares consiste à prendre en compte un nouveau paramètre
next
dans le handler, et d'y faire appel pour déclencher l'exécution du middleware suivant.
L'exemple ci-dessous fait s'enchainer 3 middlewares basiques :
app.js
const express = require('express');
const app = express();
// 1er middleware : ex. d'affichage d'informations dans la console
app.use((req, res, next) => {
const now = new Date().toDateString() ;
console.log(`${now} : une requête ${req.method} est arrivée !`);
next(); // l'appel à next() transmet les informations pour traitement dans le middleware suivant
});
// 2ème middleware : préparation de la réponse
app.use((req, res, next) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html;charset=utf-8');
next(); // l'appel à next() transmet les informations pour traitement dans le middleware suivant
});
// 3ème middelware : envoi de la réponse
app.use((req, res) => {
res.end('Le serveur Express dit <b>bonjour</b>');
});
...
La réponse de notre serveur au client n'a jusqu'à présent été constituée que de quelques méta-informations et d'une simple chaîne.
Bien entendu, Express fournit les moyens de renvoyer des réponses plus complexes sous la forme de fichiers.
Nous allons maintenant voir 2 manières de renvoyer des fichiers "statiques", c'est à dire qui ne sont pas transformés/complétés par le serveur avant d'être retournés au client.
NB : les fichiers statiques d'un serveur sont en général les feuilles de style(.css), les fichiers images, des fichiers html "pur", etc.
La méthode
res.sendFile
d'Express permet d'envoyer un fichier au client.
On peut donc utiliser cette méthode pour envoyer notre réponse sous la forme plus classique d'une page html.
Pour l'exemplifier, nous allons créer un simple fichier nommé index.html
.
La convention veut que les documents statiques (fichiers html, feuilles CSS, images, etc.)
d'un serveur web soient mis dans un dossier nommé /public
.
index.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Super Site</title>
</head>
<body>
<h1>Le serveur Express dit bonjour !!!</h1>
</body>
</html>
Il nous reste maintenant à modifier notre application Express pour qu'elle renvoie directement ce fichier au client.
NB : dans cet exemple, quelle que soit la requête du client, notre serveur renvoie la même page statique
index.html
.
Il est possible de faire en sorte qu'Express renvoie différents fichiers statiques en se basant automatiquement sur le chemin public indiqué dans la requête.
La fonction middleware express.static(root, [options])
permet à Express de servir les
fichiers statiques se trouvant sous le dossier root
.
On donne généralement le nom de public
au dossier racine contenant les
fichiers statiques.
L'exemple ci-dessous transforme l'application précédent pour qu'elle serve automatiquement tous les
fichiers statiques se trouvant dans public/
:
const express = require('express');
const app = express();
const path = require('path');
// Ajoute un middleware qui retourne les documents statiques situés sous le dossier /public.
// NB : il faut le mettre avant tout autres use qui modifie res
// pour que le cas des fichiers static soit bien traité en 1er dans la chaine des middlewares.
app.use(express.static(path.join(__dirname,'public')));
app.use((req, res, next) => {
const now = new Date().toDateString() ;
console.log(`${now} : une requête ${req.method} est arrivée !`);
next();
});
module.exports = app;
On peut maintenant accéder au fichier statique index.html
en allant à l'adresse
http://127.0.0.1:3000/pages/index.html
.
L'intérêt de cette méthode est que le serveur peut renvoyer d'autres fichiers statiques.
Pour l'exemplifier, nous allons modifier index.html
en y ajoutant une référence à
une feuille de style (un autre fichier statique) que nous mettrons dans public/css/mains.css
.
main.css
body{
background: black;
color: limegreen;
text-align: center;
}
Le fichier html modifié :
index.html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Super Site</title>
<!--Référence au fichier statique main.css-->
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<h1>Le serveur Express dit bonjour !!!</h1>
</body>
</html>
L'accès à cette page affiche maintenant le message centré en limegreen sur fond noir.
La console réseau indique que le serveur a bien envoyé les 2 fichiers statiques.
S'il est important de permettre au serveur de renvoyer des fichiers statiques (images, css, etc.), les sites web mettent généralement en oeuvre un mécanisme de réponses dynamiques basé sur des templates : le fichier html renvoyé contient des "trous" qui sont remplis dynamiquement par le serveur (au moment du traîtement de la requête) en utilisant un langage de scripts.
Il existe de nombreux frameworks qui facilitent la réalisation de sites à base de templates.
Un des plus populaires dans le cadre de Node.js et Express est EJS (Embedded JavaScript templating) qui, comme son nom l'indique, utilise Javascript comme langage de script.
npm install ejs
.
En EJS, les fichiers .html
sont remplacées par des fichiers .ejs
appellés vues (views).
Les vues EJS contiennent du code "classique" : html + css + JavaScript "client" (exécuté par le navigateur), mais aussi du code JavaScript "serveur" qui sera exécuté par le serveur avant envoi au client.
Le traitement du code Javascript "serveur" est similaire au traitement d'un code PHP dans un serveur PHP : il est interprété sur le serveur, remplacé par d'éventuelles sorties, et n'apparait en aucun cas dans la page résultante envoyée au client.
Le code Javascript "serveur" EJS est mis entre les balises <%
et %>
.
Par convention, les pages EJS sont rangées dans le dossier /views/pages/.
L'exemple ci-dessous correspond à une vue nommée home.ejs
qui peut être paramétrée par une
variable nommée user
:
La balise ouvrante <%=
permet d'effectuer une sortie dans la page.
Dans l'exemple, la ligne 19
fera afficher "Bonjour valeur_de_user".
Pour que le serveur utilise EJS, il faut préciser à Express le moteur (engine) de rendu qu'il doit utiliser. Il faut aussi indiquer à ce moteur le dossier dans lequel il pourra trouver les vues demandées.
La configuration du moteur de rendu est réalisée grâce à la méthode set
d'express.
Dans notre exemple, il faut donc compléter le contenu de app.js
avec :
app.set('view engine', 'ejs'); // Définition du moteur de rendu
app.set('views', path.join(__dirname, 'views')); // Déclaration du dossier contenant les vues
La demande de rendu est réalisée grâce à la méthode render
de l'objet res
.
Dans notre exemple (simple), on veut que le traitement de toutes les requêtes reçues par le serveur réponde
avec le rendu de la vue home.ejs
qui se trouve dans le sous-dossier pages
du
dossie des vues views
.
On écrira donc le middleware :
app.use((req, res) => {
// demande de rendu EJS
res.render('pages/home') ; // on donne le chemin dans views, et on omet le .ejs
});
On obtient le fichier app.js
:
Avec pour résultat :
L'intérêt des templates est de pouvoir les paramétrer et c'est ce que nous avons prévu avec la variable
user
utilisée dans home.ejs
.
Cependant, la version actuelle de app.js
n'envoie pas de paramètre à la vue : la variable user
n'est donc pas définie et la 1ère ligne de home.ejs
a donc pour effet de la créer en lui
affectant la valeur 'bel(le) inconnu(e)'
, ce qui explique l'affichage obtenu.
L'envoi de paramètre(s) à une vue est réalisé en ajoutant un dictionnaire de paramètre(s)
comme second argument de l'appel à la méthode render
.
Pour envoyer un user
à notre vue, il faut donc modifier l'appel à render
dans
notre app.js
avec :
res.render('pages/home', { user: 'Greg'}) ;
Une requête sur le serveur a maintenant pour résultat :
Les balises EJS peuvent aussi contenir des structures de contrôle (if
,
for
, while
, ...)
permettant de gérer la génération de portions de code client.
La modification de home.ejs
ci-dessous a le même effet que précédemment,
mais la génération est ici contrôlée
par une structure if ... else ...
(lignes 14
à 18
) :
Modifiez l'application pour que home.ejs
puisse être paramétrée par :
nickname
et sex
.
Selon la configuration des paramètres, l'application affichera un des messages suivants :
La correction se trouve ICI.
Au lieu d'utiliser 2 paramètres séparés nickname
et sex
, faites en sorte que ces
informations soient les attributs d'un seul et même paramètre nommé user
.
La correction se trouve ICI.
Les pages d'un site sont souvent constituées de parties qui sont réutilisées d'une page à l'autre comme
l'entête (header
) et le pied de page (footer
).
L'instruction <%- include('chemin') %>
permet d'inclure des parties de pages
définies dans d'autres fichiers .ejs
.
Dans notre exemple, on peut créer dans views
un nouveau sous-dossier nommé
partials
dans lequel on mettra les fichiers head.ejs
et foot.ejs
.
head.ejs
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test EJS</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
foot.ejs
</body>
</html>
Ils peuvent être importés dans home.ejs
grâce à include
, et pourraient tout à fait servir
dans d'autres pages.
Transformez l'exercice précédent pour qu'il utilise les partials
head.ejs
et foot.ejs
L'utilisation d'include
est pratique, mais cette méthode est fastidieuse lorsque l'on souhaite
inclure systématiquement des parties comme le header et le footer sur toutes les pages d'un site.
Le package
express-ejs-layouts
permet de pallier ce problème en faisant en sorte que les vues utilisent un(des)
layout(s) spécifique(s) sans avoir à ajouter include
dans chaque vue.
L'installation est à nouveau faite simplement grâce à npm
(cf.
documentation).
Pour l'utiliser dans notre application, nous définissons le layout souhaité dans un fichier
par exemple nommé layout.ejs
que nous mettons dans un sous-dossier layouts
du dossier views
.
Vous remarquerez que la ligne 15
de layout.ejs
utilise l'instruction EJS
<%- body %>
: c'est à cet endroit que sera injectée la vue
que l'on souhaitera afficher.
Notez aussi que layout.ejs
inclut les fichiers header.ejs
et footer.ejs
tels que :
header.ejs
<header>
Voici un Header
</header>
footer.ejs
<footer>
Voici un Footer
</footer>
Ce layout étant plus complexe que les pages que nous avons créées jusqu'ici, voici une nouvelle
version de notre feuille de style main.css
:
La dernière étape consiste à ajouter le middleware express-ejs-layouts
dans app.js
:
Et voici le résultat obtenu :
La correction se trouve ICI.
Transformez l'exercice précédent pour qu'il n'utilise plus des include
,
mais qu'il mette en oeuvre un layout
.