1. fs.readFile et fs.writeFile (callbacks)
Le module fs (File System) est le module natif Node.js pour interagir avec le système de fichiers.
Il propose une API asynchrone avec callbacks et une API synchrone (bloquante).
Lire un fichier — asynchrone (callback)
const fs = require('fs');
// Lecture asynchrone — non-bloquante
fs.readFile('config.txt', 'utf8', (err, contenu) => {
if (err) {
console.error('❌ Erreur lecture :', err.message);
return;
}
console.log('Contenu :', contenu);
});
console.log('Cette ligne s\'affiche AVANT le contenu du fichier !');
Lire un fichier JSON
const fs = require('fs');
fs.readFile('./data/users.json', 'utf8', (err, data) => {
if (err) { console.error('Fichier introuvable'); return; }
try {
const users = JSON.parse(data);
console.log('Utilisateurs :', users.length);
users.forEach(u => console.log(' -', u.name));
} catch (parseErr) {
console.error('JSON invalide :', parseErr.message);
}
});
Écrire un fichier
const fs = require('fs');
// Écrire (remplace le fichier s'il existe)
const contenu = 'Ligne 1\nLigne 2\nLigne 3';
fs.writeFile('output.txt', contenu, 'utf8', (err) => {
if (err) { console.error('Erreur écriture :', err); return; }
console.log('✅ Fichier écrit !');
});
// Ajouter à un fichier existant (append)
fs.appendFile('logs.txt', '[' + new Date().toISOString() + '] Log entry\n', (err) => {
if (err) console.error(err);
});
Version synchrone (bloquante)
const fs = require('fs');
// Synchrone — bloque le thread principal
// À utiliser uniquement pour les scripts (pas dans un serveur HTTP !)
try {
const contenu = fs.readFileSync('config.json', 'utf8');
const config = JSON.parse(contenu);
console.log('Config chargée :', config);
} catch (err) {
console.error('Erreur :', err.message);
}
// Écriture synchrone
fs.writeFileSync('output.txt', 'Hello synchrone !', 'utf8');
fs.readFile). Les versions Sync bloquent le thread et
paralysent toutes les requêtes entrantes.
2. fs.promises — async/await
L'API fs.promises expose les mêmes opérations sous forme de Promises,
ce qui permet d'utiliser async/await pour un code lisible et sans callback hell.
Importer fs.promises
// Méthode 1 — déstructuration (Node.js >= 10)
const { promises: fs } = require('fs');
// Méthode 2 — module séparé (Node.js >= 14)
const fs = require('fs/promises');
// Méthode 3 — accès direct
const fsPromises = require('fs').promises;
Lire et modifier un fichier JSON
const fs = require('fs/promises');
const path = require('path');
async function mettreAJourConfig() {
const fichier = path.join(__dirname, 'config.json');
try {
// Lire
const contenu = await fs.readFile(fichier, 'utf8');
const config = JSON.parse(contenu);
// Modifier
config.lastUpdate = new Date().toISOString();
config.version = (parseFloat(config.version || '1.0') + 0.1).toFixed(1);
// Réécrire
await fs.writeFile(fichier, JSON.stringify(config, null, 2), 'utf8');
console.log('✅ Config mise à jour :', config);
} catch (err) {
console.error('❌ Erreur :', err.message);
}
}
mettreAJourConfig();
Opérations multiples en parallèle
const fs = require('fs/promises');
async function lirePlusieursConfigs() {
try {
// Lire plusieurs fichiers en PARALLÈLE avec Promise.all
const [config, users, produits] = await Promise.all([
fs.readFile('config.json', 'utf8'),
fs.readFile('users.json', 'utf8'),
fs.readFile('produits.json', 'utf8')
]);
console.log('Config :', JSON.parse(config).name);
console.log('Users :', JSON.parse(users).length);
console.log('Prods :', JSON.parse(produits).length);
} catch (err) {
// Un seul catch pour toutes les erreurs
console.error('Fichier manquant :', err.message);
}
}
fs/promises + async/await
pour tout nouveau code Node.js. C'est plus lisible que les callbacks et plus simple que les Promises brutes.
3. Opérations sur les fichiers et dossiers
Au-delà de lire et écrire, fs permet de lister, créer, supprimer et déplacer
des fichiers et dossiers.
readdir — Lister les fichiers
const fs = require('fs/promises');
const path = require('path');
async function listerFichiers(dossier) {
try {
const entrees = await fs.readdir(dossier, { withFileTypes: true });
console.log('Contenu de', dossier + ' :');
for (const entree of entrees) {
const type = entree.isDirectory() ? '📁' : '📄';
console.log(' ' + type, entree.name);
}
} catch (err) {
console.error('Dossier introuvable :', err.message);
}
}
listerFichiers('./data');
stat — Informations sur un fichier
const fs = require('fs/promises');
async function infoFichier(chemin) {
const stat = await fs.stat(chemin);
console.log('Chemin :', chemin);
console.log('Taille :', stat.size, 'octets');
console.log('Dossier ? :', stat.isDirectory());
console.log('Fichier ? :', stat.isFile());
console.log('Créé le :', stat.birthtime.toLocaleString('fr-FR'));
console.log('Modifié le:', stat.mtime.toLocaleString('fr-FR'));
}
infoFichier('package.json').catch(console.error);
mkdir, unlink, rename
const fs = require('fs/promises');
async function gestionFichiers() {
// Créer un dossier (et ses parents si nécessaire)
await fs.mkdir('./data/archives/2024', { recursive: true });
console.log('✅ Dossier créé');
// Écrire un fichier test
await fs.writeFile('./data/test.txt', 'Contenu temporaire', 'utf8');
// Renommer / déplacer
await fs.rename('./data/test.txt', './data/archives/2024/test.txt');
console.log('✅ Fichier déplacé');
// Supprimer un fichier
await fs.unlink('./data/archives/2024/test.txt');
console.log('✅ Fichier supprimé');
// Supprimer un dossier vide
// await fs.rmdir('./data/archives/2024');
// Supprimer dossier et son contenu (Node >= 14.14)
// await fs.rm('./data/archives', { recursive: true });
}
gestionFichiers().catch(console.error);
fs.access(path) (throws si absent)
ou fs.stat(path).then(...).catch(...). Évitez fs.existsSync() en production.
4. path et __dirname
La gestion des chemins est cruciale pour écrire du code portable (Windows/Linux/macOS).
Node.js fournit path et __dirname pour ça.
__dirname vs process.cwd()
// __dirname = dossier du fichier courant (TOUJOURS absolu)
// process.cwd() = dossier depuis lequel node a été lancé
// Fichier : /projet/src/utils/fichier.js
console.log(__dirname); // /projet/src/utils
console.log(process.cwd()); // /projet (si lancé depuis /projet)
// BONNE PRATIQUE — utiliser __dirname pour les chemins relatifs au code
const configPath = path.join(__dirname, '..', 'config', 'app.json');
// → /projet/src/config/app.json — correct peu importe d'où node est lancé
path — Méthodes essentielles
const path = require('path');
// join — Assembler des segments (cross-platform)
path.join('/home', 'user', 'docs', 'file.txt');
// → /home/user/docs/file.txt (Linux/Mac)
// → \home\user\docs\file.txt (Windows)
// resolve — Chemin absolu depuis CWD
path.resolve('config.json'); // /current/dir/config.json
path.resolve('..', 'shared'); // Remonter d'un niveau
// Décomposer un chemin
const p = '/home/alice/photo.jpg';
path.basename(p); // 'photo.jpg'
path.basename(p, '.jpg');// 'photo' (sans extension)
path.extname(p); // '.jpg'
path.dirname(p); // '/home/alice'
// Analyser un chemin complet
path.parse('/home/alice/photo.jpg');
// { root: '/', dir: '/home/alice', base: 'photo.jpg',
// ext: '.jpg', name: 'photo' }
// Construire un chemin depuis ses parties
path.format({ dir: '/home/alice', name: 'photo', ext: '.jpg' });
// → '/home/alice/photo.jpg'
Exemple pratique : gestionnaire de config
const fs = require('fs/promises');
const path = require('path');
// Toujours basé sur __dirname → portable
const CONFIG_DIR = path.join(__dirname, 'config');
const CONFIG_FILE = path.join(CONFIG_DIR, 'app.json');
async function chargerConfig() {
try {
const data = await fs.readFile(CONFIG_FILE, 'utf8');
return JSON.parse(data);
} catch {
// Retourner une config par défaut si le fichier n'existe pas
return { port: 3000, env: 'development', debug: false };
}
}
async function sauvegarderConfig(config) {
await fs.mkdir(CONFIG_DIR, { recursive: true });
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
}
5. Streams et fs.watch
Les Streams permettent de lire ou écrire de grandes quantités de données
par morceaux (chunks) sans charger tout en mémoire. fs.watch
surveille les changements de fichiers en temps réel.
createReadStream — Lire par morceaux
const fs = require('fs');
// Lire un fichier volumineux morceau par morceau
const readable = fs.createReadStream('gros-fichier.csv', {
encoding: 'utf8',
highWaterMark: 64 * 1024 // Morceaux de 64 Ko
});
let totalChars = 0;
readable.on('data', (chunk) => {
totalChars += chunk.length;
process.stdout.write('.'); // Afficher un point par chunk
});
readable.on('end', () => console.log('\n✅ Lu ! Total :', totalChars, 'caractères'));
readable.on('error', (err) => console.error('Erreur :', err.message));
createWriteStream — Écrire par morceaux
const fs = require('fs');
const writable = fs.createWriteStream('rapport.txt', { flags: 'w' });
// Écrire plusieurs morceaux
for (let i = 1; i <= 100; i++) {
writable.write(`Ligne ${i}: données générées automatiquement\n`);
}
// Fermer le stream
writable.end(() => {
console.log('✅ Fichier écrit avec le stream !');
});
.pipe() — Connecter des streams
const fs = require('fs');
const zlib = require('zlib'); // Module natif de compression
// Copier et compresser un fichier en une ligne !
fs.createReadStream('video.mp4')
.pipe(zlib.createGzip()) // Compresser à la volée
.pipe(fs.createWriteStream('video.mp4.gz'))
.on('finish', () => console.log('✅ Fichier compressé !'));
// Copie simple sans transformation
fs.createReadStream('source.txt')
.pipe(fs.createWriteStream('copie.txt'));
fs.watch — Surveiller les changements
const fs = require('fs');
const path = require('path');
const dossierSurveille = path.join(__dirname, 'data');
console.log('👀 Surveillance de :', dossierSurveille);
console.log('Modifiez des fichiers dans ce dossier...\n');
// Surveiller un dossier
const watcher = fs.watch(dossierSurveille, { recursive: true }, (event, filename) => {
if (filename) {
const timestamp = new Date().toLocaleTimeString('fr-FR');
console.log(`[${timestamp}] Événement "${event}" sur : ${filename}`);
}
});
// Arrêter après 30 secondes
setTimeout(() => {
watcher.close();
console.log('Surveillance arrêtée.');
}, 30000);
Quand utiliser les Streams ?
| Situation | Approche recommandée |
|---|---|
| Fichier < 10 Mo | fs.readFile / fs.promises |
| Fichier > 10 Mo (logs, CSV, vidéo) | createReadStream |
| Copier un fichier volumineux | readStream.pipe(writeStream) |
| Réponse HTTP volumineuse | readStream.pipe(res) |
| Surveiller des changements en dev | fs.watch() ou chokidar |
chokidar — il est plus fiable que fs.watch() sur tous les OS.