Apprendre Node.js : Système de Fichiers

Le module fs, natif à Node.js est largement utilisé dans de nombreuses applications web afin de manipuler le système de fichiers local.

Icône de calendrier
Intermédiaire
7 chapitres

Module fs

Le besoin de manipuler des fichiers et dossiers est au cœur du développement de site/d’application web ou d’API.

Pour cela, Node.js met à notre disposition un Core Module nommé fs (acronyme de File System), fournissant des fonctionnalités pour interagir avec le système de fichiers du système d’exploitation.

Le module fs est l’un des modules les plus importants de Node.js car il permet de créer, modifier, lire et supprimer des fichiers et des dossiers, ainsi que de travailler avec les flux de fichiers.

Cela permet entre autres de :

  • Retourner une page .html au client (site web)
  • Retourner des données au format .json au client (API)
  • Écrire des informations dans un document (fichiers de config, fichiers de logs, bases de données orientées documents…)
  • Uploader et gérer des fichiers sur le serveur (images, .pdf…)

Le module fs est un module intégré de Node.js, il n’a donc pas besoin d’être installé séparément. Il peut être inclus dans une application Node.js en utilisant la fonction require() :

copié !
const fs = require('fs');

Manipuler des fichiers

Créer et écrire dans un fichier

Pour créer et/ou écrire dans fichier, fs nous propose 2 méthodes principales nommées writeFile() et appendFile().

writeFile() écrit du contenu dans un fichier, en écrasant un éventuel contenu existant.

fs.writeFile(file, data[, options], callback)

copié !
fs.writeFile('monfichier.txt', 'Texte à écrire', (err) => {
	if (err) throw err;
	console.log('Le fichier a été mis à jour');
});

appendFile() écrit du contenu à la fin d’un fichier, en préservant un éventuel contenu existant.

fs.appendFile(file, data[, options], callback)

copié !
fs.appendFile('monfichier.txt', 'Texte à ajouter', (err) => {
	if (err) throw err;
	console.log('Le fichier a été mis à jour');
});

Ces méthodes possèdent 3 paramètres obligatoires :

  • path : le chemin vers le fichier dans lequel écrire.
  • data : le contenu à écrire dans le fichier.
  • callback : la fonction de callback à exécuter. Cette méthode possède le paramètre err (détaillant une erreur éventuelle d’écriture).

Cette méthode possède aussi des paramètres optionnels pouvant être précisés entre path et callback. L’option la plus fréquente est l’encodage, que l’on spécifiera la plupart du temps à la valeur utf8.

Supprimer un fichier

Pour supprimer un fichier, fs nous propose une méthode nommée unlink().

fs.unlink(path, callback)

copié !
fs.unlink('/chemin/vers/monfichier.txt', (err) => {
	if (err) throw err;
	console.log('Le fichier a été supprimé');
});

Cette méthode possède 2 paramètres obligatoires :

  • path : le chemin vers le fichier à supprimer.
  • callback : la fonction de callback à exécuter. Cette méthode possède le paramètre err (détaillant une erreur éventuelle de suppression).

Renommer un fichier

Pour renommer un fichier, fs nous propose une méthode nommée rename().

fs.rename(oldPath, newPath, callback)

copié !
fs.rename('/chemin/vers/monfichier.txt', '/chemin/vers/monfichierrenomme.txt', (err) => {
	if (err) throw err;
	console.log('Le fichier a été renommé');
});

Cette méthode possède 3 paramètres obligatoires :

  • oldPath : le chemin vers le fichier à renommer.
  • newPath : le chemin vers le fichier, une fois renommé.
  • callback : la fonction de callback à exécuter. Cette méthode possède le paramètre err (détaillant une erreur éventuelle de renommage).

Lire un fichier

Pour lire un fichier, fs nous propose une méthode nommée readFile().

fs.readFile(path[, options], callback)

copié !
fs.readFile('monfichier.txt', 'utf8', (err, data) => {
	if (err) throw err;
	console.log(data);
});

Cette méthode possède 2 paramètres obligatoires :

  • path : le chemin vers le fichier à lire.
  • callback : la fonction de callback à exécuter. Cette méthode possède les paramètres err (détaillant une erreur éventuelle de lecture) et data (le contenu du fichier).

Cette méthode possède aussi des paramètres optionnels pouvant être précisés entre path et callback. L’option la plus fréquente est l’encodage, que l’on spécifiera la plupart du temps à la valeur 'utf8'.

Retourner une page web

Nous pouvons désormais utiliser la méthode fs.readFile() au sein de notre serveur afin de retourner une page web au client.

copié !
const http = require('http');
const fs = require('fs');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
	res.setHeader('Content-Type', 'text/html'); 
	fs.readFile('index.html', 'utf8', function(err, data) {
		if (err) {
			res.statusCode = 404;
			res.end('File not found.');
		} else {
			res.statusCode = 200;
			res.end(data);
		}
	});
});

server.listen(port, hostname, () => {
	console.log(`Serveur démarré sur http://${hostname}:${port}`);
});
  1. On importe le module fs.
  2. On modifie le content-type de l’entête en text/html.
  3. On utilise notre module fs pour lire le fichier 📄 index.html via la méthode readFile().
  4. S’il y a une erreur de lecture, on renvoit une erreur 404.
  5. S’il n’y a pas d’erreur, on attache les données à la réponse HTTP avec res.end().

Manipuler des dossiers

Créer un dossier

Pour créer un nouveau dossier, fs nous propose une méthode nommée mkdir().

fs.mkdir(path[, options], callback)

copié !
fs.mkdir('./chemin/vers/monnouveaudossier', (err) => {
	if (err) throw err;
	console.log('Le dossier a été créé');
});

Cette méthode possède 2 paramètres obligatoires :

  • path : le chemin vers le fichier à créer.
  • callback : la fonction de callback à exécuter. Cette méthode possède le paramètre err (détaillant une erreur éventuelle de création).

Renommer un dossier

Pour renommer un dossier, on utilise fs.rename(), comme pour renommer un fichier. N’oubliez pas d’ajouter ./ au début des chemins vers vos dossiers.

Supprimer un dossier

Pour supprimer un dossier et tout son contenu, fs nous propose une méthode nommée rmdir().

fs.rmdir(path[, options], callback)

copié !
fs.rmdir('./chemin/vers/mondossier', { recursive: true }, (err) => {
	if (err) throw err;
	console.log('Le dossier a été supprimé');
});

Cette méthode possède 2 paramètres obligatoires :

  • path : le chemin vers le dossier à supprimer.
  • callback : la fonction de callback à exécuter. Ce callback possède le paramètre err (détaillant une erreur éventuelle de suppression).

Lire le contenu d’un dossier

Pour lire le contenu d’un dossier (lister les sous-dossiers et fichiers), fs nous propose la méthode readdir().

fs.readdir(path[, options], callback)

copié !
fs.readdir('./chemin/vers/mondossier', (err, files) => {
	if (err) throw err;
	console.log(files);
});

Cette méthode possède 2 paramètres obligatoires :

  • path : le chemin vers le dossier à lire.
  • callback : la fonction de callback à exécuter. Ce callback possède le paramètre err (détaillant une erreur éventuelle de suppression) et files (listant dans un tableau les sous-dossiers et fichiers qu’il contient).

Synchrone VS Asynchrone

Les méthodes listées ci-dessus sont par défauts implémentées de manière asynchrone grâce à l’utilisation d’opérations d’entrée/sortie non bloquantes ; elles peuvent ainsi s’exécuter en parallèle.

En savoir plus

Le système de fichiers de Node.js utilise une API d’Entré/Sortie (E/S) non bloquante pour interagir avec le système de fichiers. Lorsque vous appelez une méthode d’E/S, telle que readFile(), Node.js envoie une demande d’E/S au système de fichiers sous-jacent, qui est gérée de manière asynchrone en arrière-plan par le système d’exploitation.

Pendant que l’opération d’E/S est en cours, le thread principal de Node.js peut continuer à exécuter d’autres tâches sans être bloqué. Lorsque l’opération d’E/S est terminée, le système d’exploitation envoie une notification à Node.js pour indiquer que les données sont prêtes à être lues. À ce stade, Node.js récupère les données lues depuis le système de fichiers et appelle la fonction de rappel en conséquence.

Ainsi, l’utilisation d’opérations d’E/S non bloquantes permet à Node.js de continuer à exécuter d’autres tâches pendant que des opérations d’E/S sont en cours, ce qui améliore les performances et l’évolutivité de l’application.

Pour certains besoins, Node.js proposent d’utiliser des méthodes synchrones.

Il convient alors de les utiliser comme il se doit.

❌ Méthodes asynchrones successives

copié !
fs.appendFile('monfichier.txt', 'Texte à ajouter', () => {});

fs.readFile('monfichier.txt', 'utf8', (err, data) => {
	if (err) throw err;
	console.log(data);
});

Dans cet exemple, il est tout à fait possible que la méthode readFile() termine son exécution avant la méthode appendFile(), ce qui s’avère embêtant dans le sens où je m’apprête ici à lire un fichier pas encore mis à jour…

Les méthodes readFile(), appendFile(), rename()… possèdent une méthode équivalente mais en version synchrone, bloquant l’exécution du programme jusqu’à ce que l’opération en cours soit terminée.

✅ Méthodes synchrones successives

Les méthodes synchrones de Node.js possèdent alors le suffixe « Sync » (readFileSync(), appendFileSync(), renameSync(), etc.).

Ainsi, ces méthodes synchrones ne possèdent pas de fonction de callback en paramètre.

Le code maladroit précédent pourra ainsi être amélioré ainsi :

copié !
fs.appendFileSync('monfichier.txt', 'Texte à ajouter');

fs.readFile('monfichier.txt', 'utf8', (err, data) => {
	if (err) throw err;
	console.log(data);
})

Ici, readFile() va attendre que appendFileSync() soit terminé avant de s’exécuter.

✅ Méthodes asynchrones avec fonction de rappel

Si une opération sur un fichier doit être exécuté après l’exécution d’une première, on privilégiera l’exécution différée via les fonctions de callbacks.

copié !
fs.appendFile('monfichier.txt', 'Texte à ajouter', (err) => {
	if (err) throw err;
	fs.readFile('monfichier.txt', 'utf8', (err, data) => {
		if (err) throw err;
		console.log(data);
	});
});

Ici, readFile() va s’exécuter lorsque que appendFile() aura terminé l’ajout de texte puisqu’il readFile() est déclenché au sein de la fonction de callback.

De plus, notez que si un traitement autre devait être effectué sans attendre la fin de l’écriture / lecture du fichier 📄 monfichier.txt, comme par exemple l’envoi d’un email (simulé ici par un console.log()), cela pourrait être effectué en parallèle grâce à la nature asynchrone de la méthode fs.appendFile() qui ne bloque pas l’exécution du script.

copié !
fs.appendFile('monfichier.txt', 'Texte à ajouter', (err) => {
	if (err) throw err;
	console.log('Le fichier a été mis à jour');
	fs.readFile('monfichier.txt', 'utf8', (err, data) => {
		if (err) throw err;
		console.log(data);
	});
});

console.log('Ici, un script qui envoi un email...'); // 🚀 Je ne perds pas de temps à attendre