Leçon 3 / 8
Leçon 03 · Node.js

Système de fichiers

Le module fs

Node.js embarque en natif le module fs (File System) qui donne accès au système de fichiers du serveur : lecture, écriture, suppression, liste de dossiers, etc. Aucune installation nécessaire — il fait partie de la bibliothèque standard de Node.

Il existe trois façons d'utiliser fs selon le style de code que tu préfères :

  • Synchrone — bloque l'exécution jusqu'à la fin de l'opération (fs.readFileSync)
  • Callbacks — style Node.js historique, n'attend pas (fs.readFile)
  • Promesses — style moderne avec async/await via fs/promises
// Import façon CommonJS (la plus courante dans Node.js)
const fs = require('fs');

// Import de la version promesses (recommandé pour le code moderne)
const fs = require('fs/promises');

// Import ES Modules (si "type": "module" dans package.json)
import fs from 'fs/promises';

Lire un fichier

Trois syntaxes pour lire un fichier. Le résultat est un Buffer par défaut ; passe 'utf8' pour obtenir directement une chaîne de caractères.

const fs = require('fs');

// 1. Callback — non-bloquant, résultat dans la fonction
fs.readFile('notes.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// 2. Synchrone — bloque jusqu'à la fin (à éviter dans un serveur)
const contenu = fs.readFileSync('notes.txt', 'utf8');
console.log(contenu);

// 3. Promesses — moderne, compatible async/await
const fsp = require('fs/promises');

async function lireFichier() {
  const contenu = await fsp.readFile('notes.txt', 'utf8');
  console.log(contenu);
}

lireFichier();
💡

Préfère fs/promises avec async/await dans tout nouveau code. C'est plus lisible, plus facile à déboguer et s'intègre parfaitement avec Express et les autres API modernes de Node.js.

Écrire un fichier

writeFile crée le fichier s'il n'existe pas ou le remplace entièrement. Pour ajouter du contenu à la fin sans effacer l'existant, utilise appendFile.

const fsp = require('fs/promises');

async function ecrire() {
  // Crée ou remplace le fichier
  await fsp.writeFile('journal.txt', 'Bonjour !\n', 'utf8');

  // Ajoute une ligne à la fin sans effacer le contenu existant
  await fsp.appendFile('journal.txt', 'Nouvelle entrée\n', 'utf8');

  console.log('Fichier écrit !');
}

ecrire();

Vérifier l'existence d'un fichier

Avant de lire ou d'écrire, il est souvent utile de vérifier si un fichier existe. existsSync est simple mais synchrone. stat donne des informations détaillées (taille, date, type) de façon asynchrone.

const fs = require('fs');
const fsp = require('fs/promises');

// Synchrone — retourne true ou false
if (fs.existsSync('config.json')) {
  console.log('Le fichier existe');
}

// Stat — donne taille, dates, type (fichier ou dossier ?)
async function infos() {
  try {
    const stats = await fsp.stat('config.json');
    console.log('Taille :', stats.size, 'octets');
    console.log('C\'est un dossier ?', stats.isDirectory());
    console.log('Modifié le :', stats.mtime);
  } catch (err) {
    console.log('Fichier introuvable');
  }
}

infos();

Lister un dossier

readdir retourne un tableau contenant le nom de tous les fichiers et sous-dossiers présents dans un répertoire.

const fsp = require('fs/promises');

async function listerDossier() {
  const fichiers = await fsp.readdir('./uploads');
  console.log(fichiers);
  // ['image.png', 'document.pdf', 'notes.txt']

  // Pour avoir aussi le type (fichier / dossier)
  const details = await fsp.readdir('./uploads', { withFileTypes: true });
  details.forEach(entry => {
    const type = entry.isDirectory() ? 'dossier' : 'fichier';
    console.log(`${entry.name} — ${type}`);
  });
}

listerDossier();

Créer et supprimer

mkdir crée un dossier. L'option recursive: true crée tous les intermédiaires manquants sans erreur si le dossier existe déjà. rm supprime un fichier ou un dossier entier. unlink ne supprime que des fichiers.

const fsp = require('fs/promises');

// Créer un dossier (recursive = ne plante pas si existe déjà)
await fsp.mkdir('./uploads/images', { recursive: true });

// Supprimer un fichier
await fsp.unlink('./uploads/ancien.jpg');

// Supprimer un dossier et tout son contenu (recursive)
await fsp.rm('./uploads/temp', { recursive: true, force: true });

// Renommer ou déplacer un fichier
await fsp.rename('./old-name.txt', './new-name.txt');
⚠️

fs.rm avec recursive: true supprime définitivement tout le contenu du dossier, sans passer par la corbeille. Assure-toi de bien viser le bon chemin avant d'exécuter cette commande.

Streams : lire les gros fichiers

Pour les fichiers volumineux (logs, vidéos, exports CSV…), charger tout le contenu en mémoire avec readFile peut saturer le serveur. Les streams lisent le fichier par morceaux (chunks) au fur et à mesure, sans tout charger d'un coup.

const fs = require('fs');

// Créer un flux de lecture
const stream = fs.createReadStream('./gros-fichier.csv', { encoding: 'utf8' });

// Chaque morceau arrive ici
stream.on('data', (chunk) => {
  console.log('Reçu :', chunk.length, 'caractères');
});

stream.on('end', () => {
  console.log('Lecture terminée');
});

stream.on('error', (err) => {
  console.error('Erreur :', err.message);
});

// Astuce : piper directement vers une réponse HTTP
// stream.pipe(res);  — envoie le fichier au client sans tout charger

Le module path

Le module path aide à construire des chemins de fichiers de façon fiable, quel que soit l'OS (Windows utilise \, Linux/Mac utilisent /). Il fait aussi partie de la bibliothèque standard de Node.js.

const path = require('path');

// __dirname = dossier du fichier actuel (chemin absolu)
console.log(__dirname);
// /home/user/monapp/src

// path.join — assemble des segments de chemin intelligemment
const chemin = path.join(__dirname, 'data', 'users.json');
// /home/user/monapp/src/data/users.json

// path.resolve — chemin absolu depuis n'importe où
const absolu = path.resolve('config.json');
// /home/user/monapp/config.json (résolu depuis le dossier courant)

// path.extname — extension du fichier
path.extname('photo.jpg');    // '.jpg'
path.extname('archive.tar.gz'); // '.gz'

// path.basename — nom du fichier sans le chemin
path.basename('/data/users.json');       // 'users.json'
path.basename('/data/users.json', '.json'); // 'users'

// path.dirname — dossier parent d'un chemin
path.dirname('/data/users.json'); // '/data'
💡

Utilise toujours path.join(__dirname, ...) plutôt que de concaténer des chaînes manuellement (__dirname + '/data/' + fichier). path.join gère les séparateurs selon l'OS et évite les doubles slashes.

// À retenir
  • require('fs/promises') + async/await est l'approche recommandée pour le code moderne.
  • readFile lit, writeFile écrase, appendFile ajoute, unlink supprime un fichier.
  • existsSync vérifie l'existence en synchrone ; stat donne les détails en asynchrone.
  • mkdir({ recursive: true }) crée un dossier sans planter si les parents existent déjà.
  • Pour les gros fichiers, utilise createReadStream pour éviter de saturer la mémoire.
  • path.join(__dirname, ...) pour construire des chemins portables selon l'OS.