Dans cette vidéo, nous allons commencer à réaliser le back-end de notre site Internet, c'est-à-dire l'application qui va générer les pages HTML et les retourner à l'internaute.

Pour développer cette application, nous allons utiliser la plateforme Node.js et l'environnement (ou framework) ExpressJS.

Regarder la vidéo sur YouTube

Cet article est la 6e partie d'une série qui explique comment réaliser un site Internet de A à Z. Vous n'avez pas besoin d'avoir lu ou vu les parties précédentes pour comprendre cette partie. Les autres épisodes sont disponibles ici.

Si vous ne pouvez pas regarder la vidéo, un compte rendu est proposé plus bas.

Résumé de la vidéo

Jusqu'à présent, nous avons réalisé les pages de notre site manuellement, c'est-à-dire que nous utilisions un éditeur de texte, comme VSCode, et on écrivait le code de chaque page dans un document HTML. La page HTML était ensuite envoyé à l'internaute, sans qu'il y ait aucune modification.

Pour afficher la page, on ne demandait pas simplement au navigateur d'afficher les pages, mais on utilisait un petit serveur web : http-server.

Cela nous permettait de contourner les restrictions de sécurité des navigateurs, qui limitent l'exécution de certains scripts et empêchent l'affichage de certaines images lorsque la page est chargée localement, à partir du système de fichiers.

Cela nous permettait aussi, en quelques clics, de tester les pages que nous développions et de voir le résultat le navigateur.

Problématique des pages statiques

Lorsqu'on ajoute plusieurs pages à notre site, il peut être très vite pénible de réaliser toutes les mains à la main, car cela impliquerait que les informations communes, comme l'en-tête de nos pages, les informations de bas de pages ou les liens de navigations, soient recopiées manuellement sur chaque page.

On préfèrerait que nos pages soient générées dynamiquement, et que notre en-tête et notre pied de page soit injectés automatiquement.

Le fait que les pages de notre site soient statiques ne nous permet pas non plus d'optimiser ou de personnaliser l'apparence de la page en fonction de certains critères. Par exemple : notre page d'accueil est censée afficher les derniers articles. Ce serait bien de pouvoir récupérer ces derniers articles automatiquement.

Globalement, on aimerait ne plus retourner des pages statiques, mais générer les pages dynamiquement, à partir de plusieurs éléments et potentiellement de plusieurs sources de données.

Pour réponse à cette problématique et nous permettre plus tard de réaliser d'autres fonctionnalités, nous pouvons générer les pages avec une application Node.js.

Environnements Node.js et ExpressJS

Comme nous l'avons déjà vu, la plateforme Node.js permet d'écrire des programmes en utilisant le langage JavaScript, comme on le fait traditionnellement dans nos pages Web. On peut donc utiliser nos compétences de développeur front-end pour réaliser des programmes qui sont exécutés sur un serveur.

Node.js a aussi l'avantage de pouvoir accéder à un gestionnaire de paquets comme npm, comme on l'a fait dans l'épisode précédent, lorsqu'on a utilisé un paquet pour convertir des fichiers SASS en CSS.

Concrètement, l'application que l'on va réaliser va accepter en entrée des requêtes HTTP, que les navigateurs de nos visiteurs nous enverront en visitant notre site, et retourner d'autres requêtes HTTP avec le contenu de la page demandée.

Pour nous faciliter le travail, nous allons utiliser le framework Express, qui propose un certain nombre de fonctionnalités pour traiter les requêtes HTTP entrantes et générer de requêtes HTTP sortantes.

Hello World avec Node.js / Express

Avant de pouvoir utiliser Express, nous devons initialiser un nouveau projet, de la même manière que l'épisode précédent, en utilisant la commande : npm init -y

On peut ensuite installer le paquet express avec la commande : npm install express

Cela a pour conséquence d'ajouter une dépendance au fichier package.json, avec le numéro de la version installée. Dans mon cas, version 4.17.1.

On peut ensuite écrire quelques lignes de codes pour créer notre première application express :

// Importe le paquet express
const express = require('express')

// Créé une application express
const app = express()

// Crée une route "/" correspondant à la racine du site.
app.get('/', (req, res) => {
  // Fonction éxecutée lorsque une requête de type GET / est reçue.
  // Req = Request (EN) = Requête entrante
  // Res = Response (EN) = Réponse (FR) = Réquête sortante
  res.send('Hello World !')
})

// Démarrer le serveur et écouter un port donné
const PORT = 6300

app.listen(PORT, () => {
  // Fonction éxecutée lorsque l'application a démarré
  console.log(`Serveur démarré : http://localhost:${PORT}`)
})

Pour le moment, une seule et unique route a été ajoutée à notre application Express. Cela veut dire que l'application répond seulement si l'internaute visite notre page d'accueil, située à la racine / du site.

Il est aussi intéressant de noter que la route que l'on a ajoutée à notre application Express utilise la fonction get(url, callback). Cette fonction veut dire que le serveur écoute uniquement les requêtes entrantes de type GET.

GET est une méthode HTTP utilisée par les navigateurs lorsque des pages Internet sont demandées au serveur. D'autres méthodes existent, les principales sont :

  • GET pour récupérer une ressource ;
  • POST pour créer une nouvelle ressource ;
  • PUT pour modifier une ressource ;
  • DELETE pour supprimer une ressource.

Enfin, on a choisi un port spécifique pour notre application Express car l'adresse de l'application est toujours composée de deux éléments : l'adresse IP et le port.

Notre serveur est local, donc l'adresse sera 127.0.0.1 ou localhost, et le port doit choisi de sorte qu'il n'interfère pas avec d'autres applications. Par défaut, le port 80 est utilisé par HTTP et le port 443 est utilisé pour HTTPS. Cela veut dire que si on se rend sur l'adresse http://localhost/ sans préciser de port, le navigateur utilisera le port 80, c'est-à-dire http://localhost:80/.

On peut maintenant démarrer notre application en utilisant la commande node /chemin/vers/app.js, se rendre sur http://localhost:6300/ à partir du navigateur, et constater que notre application répond Hello World !.

Générer une page HTML sous Node.js/Express

Au lieu retourner un simple texte, on peut retourner la page HTML que l'on a créée dans les épisodes précédents.

La logique permettant cette page HTML va être créé dans un document séparé, pour faciliter la lecture.

On peut adapter notre route pour faire référence cet autre document :

// Importer la logique de la page d'accueil
const genererPageAccueil = require('./pages/index-get.js')

// Ecoute la méthode GET et la route '/'
app.get('/', async(req, res) => {
  // Récupérer le contenu de la page HTML
  const indexHtml = await genererPageAccueil()

  // Envoyer le résultat
  res.send(indexHtml)
})

Et créer la logique :

const { readFile } = require('fs')
const { promisify } = require('util')

// Convertie la fonction en Promise plutôt qu'en callback
// pour pouvoir utiliser async/await
const readFileAsync = promisify(readFile)

const READ_OPTIONS = { encoding: 'UTF-8' }
const INDEX_URL = 'c:/chemin/vers/index.html'

module.exports = async() => {
  // Récupérer le contenu du fichier HTML
  const contenu = await readFileAsync(INDEX_URL, READ_OPTIONS)

  // Retourner la page HTML
  return contenu
}

On peut utiliser la fonction native de Node.js readFile pour lire le contenu du fichier qui contient le code HTML de la page d'accueil.

Il est important de noter qu'il est préférable d'utiliser des fonctions asynchrones plutôt que des fonctions synchrones pour ne pas bloquer l'exécution de Node.js.

Node.js ne peut pas réaliser plusieurs opérations en parallèle. Si une fonction de notre application prend un certain temps, l'ensemble de l'application sera donc bloquée, et les requêtes des autres visiteurs seront en attente. En utilisant des fonctions asynchrones, plusieurs requêtes peuvent être traitées, car Node.js peut commencer à traiter une autre requête en attendant le résultat d'une opération asynchrone de la première. On utilise donc la version asynchrone readFile et non pas readFileSync.

On utilise aussi la fonction promisify pour ne pas avoir à utiliser de callback mais utiliser les mots-clés async et await (plus d'infos sur les callbacks, les Promises, et async/await dans cette vidéo).

On peut à nouveau exécuter notre application, et rafraichir la page.

Le texte de la page s'affiche correctement, mais les feuilles et style CSS, les images, et toutes les ressources extérieures à la page ne sont pas affichées, car nous n'avons pas ajouté de routes pour ces documents.

Retourner des fichiers statiques sous Node.js/Express

Normalement, un serveur Web, comme nginx, devrait être utilisé pour retourner des fichiers statiques, mais pas Node.js ou Express, qui ne sont pas des outils adaptés pour ce cas d'utilisation.

On installera et utilisera un serveur Web dans les prochaines vidéos, mais pour le moment on va se contenter de réaliser cette opération dans notre application Node.

Notre page HTML inclut des feuilles de styles situées dans le répertoire styles/ et des images dans le répertoire images/. On va donc ajouter à notre application ces deux répertoires en tant que routes, comme la documentation le propose:

// Ecoute les requêtes d'un chemin spécifique '/styles/xxx'
// Et associe les au repertoire donné
app.use('/styles', express.static('C:/dev/youtube/blog/code/styles/'))
app.use('/images', express.static('C:/dev/youtube/blog/code/images/'))

À noter qu'on n'a pas utilisé app.get() mais app.use() pour indiquer que les routes sont associées toutes les requêtes qui commencent par un certain chemin, dans notre cas : http://localhost:6300/styles/xxx et http://localhost:6300/images/xxx.

On peut rafraichir notre application, qui devrait à présent d'afficher correctement.

Le résultat final pour l'utilisateur est identique à ce qu'on faisait auparavant avec un simple serveur Web. La différence, c'est que nous pouvons à présent développer des fonctionnalités pour nous permettre d'ajouter des éléments dynamiques à la page.

FAQ : pourquoi utiliser des chemins absolus ?

Dans cet exemple, les chemins vers les fichiers et répeertoires sont écrits en dur (c:/dev/...), ce qui est une mauvaise pratique.

Il serait préférable de, soit :

  • utiliser des variables d'environnement, comme __dirname (docs), qui permet d'indiquer des chemins relatifs au module en cours ;
  • utiliser un fichier de configuration qui contiendrait, notamment, les chemins utilisés dans le projet. Je pense, par exemple, au module dotenv qui est beaucoup utilisé.

MAJ du 21/10/2021 : ajout de la question sur les chemins absolus.