2.1Modules natifs : fs, path, os
Node fournit des modules natifs (built-in) — des bibliothèques intégrées, disponibles sans rien installer, qui donnent accès aux capacités du système. C'est ce qui fait de Node bien plus qu'un simple exécuteur de JavaScript : il peut interagir avec les fichiers, le réseau, le système d'exploitation. On les importe par leur nom seul (require('fs') ou import fs from 'fs' ; on peut aussi préfixer node: — require('node:fs') — pour être explicite). Le plus utilisé est fs (file system) : lire, écrire, modifier, supprimer des fichiers et des dossiers. Point crucial hérité du niveau 1 : la plupart des fonctions de fs existent en deux versions — asynchrone (non-bloquante, à privilégier — fs.readFile(...) avec callback, ou fs.promises.readFile(...) avec await) et synchrone (bloquante, suffixée Sync — fs.readFileSync(...)). En pratique serveur, on utilise l'asynchrone (surtout fs.promises avec async/await, moderne et lisible) pour ne pas bloquer la boucle d'événements ; le synchrone ne se justifie que dans des cas précis (scripts simples, chargement de config au démarrage).
Deux autres modules natifs fondamentaux accompagnent souvent fs. Le module path : manipuler les chemins de fichiers de façon portable (indépendante du système d'exploitation). C'est essentiel car les chemins diffèrent entre systèmes (séparateur / sur macOS/Linux, \ sur Windows). Au lieu de concaténer des chemins à la main (fragile et non portable), on utilise path.join(__dirname, 'data', 'fichier.txt') qui assemble les segments avec le bon séparateur selon le système. path offre aussi path.resolve() (chemin absolu), path.basename(), path.extname() (extension), path.dirname()… — les outils pour travailler proprement avec les chemins. Le module os : accéder aux informations du système d'exploitation — os.platform() (le système), os.cpus() (les processeurs, utile pour le clustering, section 5-2), os.totalmem()/os.freemem() (la mémoire), os.homedir(), os.tmpdir() (dossiers standard). D'autres modules natifs importants existent (http pour les serveurs — section 2-4, crypto pour le chiffrement/hachage, events pour l'EventEmitter — section 2-2, stream — section 2-3, util, url…). L'intérêt de connaître les modules natifs est double : d'abord, ils couvrent des besoins courants sans dépendance externe (moins de paquets à installer, plus léger, plus sûr) ; ensuite, ils sont le FONDEMENT sur lequel reposent les bibliothèques (Express, par exemple, repose sur le module http) — les comprendre, c'est comprendre ce qui se passe « sous le capot ». Vous n'avez pas à tous les mémoriser, mais savoir qu'ils existent, connaître les principaux (fs, path, os, http, crypto) et savoir consulter la documentation officielle est une compétence de base du développeur Node.
Vocabulaire de la section
- Modules natifs (built-in)
- Bibliothèques intégrées à Node (aucune installation), donnant accès au système : fs, path, os, http, crypto, events, stream… Importées par leur nom.
- fs (file system)
- Module de lecture/écriture de fichiers et dossiers ; fonctions en version asynchrone (à privilégier, fs.promises + await) ou synchrone (Sync, bloquante).
- Asynchrone vs synchrone (fs)
- L'asynchrone (non-bloquant) est à privilégier en serveur pour ne pas bloquer la boucle d'événements ; le synchrone (Sync) est réservé aux scripts/config au démarrage.
- path
- Module de manipulation portable des chemins (path.join, resolve, basename, extname…) : gère les différences de séparateur entre systèmes (/ vs \).
- os
- Module d'accès aux informations système : plateforme, processeurs (os.cpus), mémoire, dossiers standard (homedir, tmpdir).
Version asynchrone ou synchrone de fs en serveur ?
En pratique — Utiliser les modules natifs
- Lisez et écrivez un fichier avec `fs.promises` et async/await (version asynchrone, non-bloquante) ; comparez à `fs.readFileSync` (synchrone, bloquante).
- Construisez des chemins portables avec `path.join(__dirname, 'dossier', 'fichier.txt')` plutôt qu'en concaténant à la main.
- Extrayez des informations d'un chemin : `path.basename`, `path.extname`, `path.dirname`.
- Affichez des infos système avec `os` : `os.platform()`, `os.cpus().length`, `os.freemem()`.
Points clés à retenir
- Les MODULES NATIFS (fs, path, os, http, crypto…) sont intégrés à Node (aucune installation) et donnent accès au système ; importés par leur nom.
- `fs` (fichiers) : privilégier l'ASYNCHRONE (fs.promises + await, non-bloquant) en serveur ; le synchrone (Sync) bloque la boucle d'événements (réservé aux scripts/config).
- `path` : manipuler les chemins de façon PORTABLE (path.join gère le bon séparateur / vs \) — ne jamais concaténer les chemins à la main.
- `os` : infos système (plateforme, os.cpus pour le clustering, mémoire). Les natifs sont le FONDEMENT des bibliothèques (Express repose sur http) — les connaître = comprendre « sous le capot ».
Questions fréquentes
Version asynchrone ou synchrone de fs : laquelle utiliser, et quand le synchrone est-il acceptable ?
C'est une décision qui touche directement à la performance de votre application, et la règle — privilégier l'asynchrone en serveur — découle du modèle non-bloquant de Node (section 0-1). Rappelons l'enjeu. Node est mono-thread avec une boucle d'événements ; une opération BLOQUANTE (synchrone) monopolise ce thread pendant toute sa durée, empêchant Node de faire autre chose — y compris servir d'autres requêtes. La version synchrone de fs (les fonctions suffixées Sync : readFileSync, writeFileSync…) BLOQUE : le code s'arrête et attend que l'opération fichier soit terminée avant de continuer. Sur un serveur, c'est problématique : pendant que readFileSync lit un gros fichier (ce qui peut prendre du temps), le serveur ne peut RIEN faire d'autre — il ne répond à AUCUN autre utilisateur. Un seul appel synchrone lent peut « geler » votre serveur pour tous les clients simultanés. C'est exactement ce que Node cherche à éviter. La version asynchrone (non-bloquante) — fs.readFile avec callback, ou mieux fs.promises.readFile avec await — ne bloque PAS : l'opération se lance, Node continue à traiter d'autres choses (d'autres requêtes), et le résultat arrive via un callback/une Promise quand c'est prêt. Le serveur reste réactif pour tous. C'est le comportement voulu, aligné avec l'essence de Node. La règle : en serveur / application réelle, utilisez TOUJOURS l'asynchrone (idéalement fs.promises + async/await, moderne et lisible). C'est non négociable pour un serveur qui sert plusieurs utilisateurs — le synchrone y serait une faute de performance grave. Quand le SYNCHRONE est acceptable (les exceptions) : (1) Scripts simples et ponctuels : un petit script CLI qui lit un fichier, fait un traitement, écrit un résultat, puis se termine — pas de serveur, pas d'utilisateurs simultanés à servir. Le blocage n'a aucune conséquence (le script fait UNE chose à la fois de toute façon), et le synchrone est plus simple à écrire (pas d'async/await). Pour ce genre d'outils, le synchrone est acceptable, voire pratique. (2) Chargement de configuration au DÉMARRAGE : lire un fichier de config UNE fois, au lancement du serveur, AVANT qu'il ne commence à servir des requêtes. À ce moment, aucun utilisateur n'est encore connecté, donc bloquer un instant n'a pas d'impact — et c'est parfois plus simple. C'est un cas d'usage classique et accepté du synchrone. (3) Certains outils de build/scripts de développement qui s'exécutent en dehors du serveur. La règle mentale : « Est-ce que ce code s'exécute pendant que le serveur sert des utilisateurs ? » Si OUI → asynchrone obligatoire (ne jamais bloquer la boucle d'événements pendant le service). Si NON (script isolé, config au démarrage avant le service) → le synchrone est acceptable. En cas de doute, ou dans du code de serveur, choisissez l'asynchrone : c'est le choix sûr et aligné avec Node. Conseil pratique : privilégiez fs.promises avec async/await pour votre code asynchrone (lisible, moderne) plutôt que les callbacks — vous obtenez le non-bloquant ET la clarté. Réservez le Sync aux scripts simples et au chargement de config au démarrage, en sachant précisément pourquoi. Cette discipline (asynchrone par défaut en serveur) est essentielle : un débutant qui truffe son serveur d'appels synchrones crée une application qui semble marcher en test (un seul utilisateur) mais s'effondre en charge (le serveur gèle dès que plusieurs requêtes lentes arrivent). Comprendre POURQUOI (ne pas bloquer la boucle) vous fait choisir juste. C'est une application directe du principe fondamental de Node vu dès le niveau 0.
Pourquoi utiliser le module path plutôt que construire les chemins à la main ?
Construire les chemins avec le module path plutôt qu'en concaténant des chaînes est une bonne pratique importante qui évite des bugs subtils, surtout de PORTABILITÉ entre systèmes d'exploitation. Voyons pourquoi. Le problème de la concaténation manuelle : on pourrait être tenté d'écrire un chemin ainsi : const chemin = __dirname + '/data/' + nomFichier;. Cela « marche » souvent... jusqu'à ce que ça casse, pour plusieurs raisons : (1) Le séparateur diffère entre systèmes. Sur macOS/Linux, le séparateur de chemin est / (slash) ; sur Windows, c'est \ (backslash). Un chemin construit avec des / en dur peut poser problème sur Windows (et vice versa). Votre code fonctionne sur votre Mac mais échoue sur le serveur Windows d'un collègue — un bug de portabilité classique et pénible. (2) Les doubles séparateurs ou séparateurs manquants. En concaténant à la main, on crée facilement des erreurs : dossier + '/' + sousDossier peut donner data//fichier (double slash) si dossier finit déjà par un slash, ou datafichier (collé) si on oublie le séparateur. Ces erreurs sont fréquentes et sources de bugs (« fichier introuvable »). (3) La gestion des chemins relatifs/absolus est délicate à faire à la main. Ce que path résout : le module path connaît les règles de chemin du système sur lequel il s'exécute et les applique correctement. (1) path.join(...) assemble des segments avec le BON séparateur (selon le système) et gère les cas limites (pas de double séparateur, pas d'oubli) : path.join(__dirname, 'data', nomFichier) produit un chemin correct sur TOUS les systèmes. C'est la fonction la plus utilisée — utilisez-la pour construire des chemins. (2) path.resolve(...) produit un chemin ABSOLU (résolu depuis le répertoire courant), utile quand vous avez besoin d'un chemin complet non ambigu. (3) path.basename(chemin) extrait le nom du fichier ; path.dirname(chemin) le dossier ; path.extname(chemin) l'extension (.txt). Pratique pour analyser des chemins sans faire du découpage de chaîne à la main. (4) path.sep donne le séparateur du système si besoin. Les bénéfices concrets : (1) Portabilité : votre code fonctionne identiquement sur macOS, Linux, Windows — crucial car vous développez peut-être sur un système et déployez sur un autre (souvent Linux en production). (2) Robustesse : pas d'erreurs de double séparateur ou de séparateur manquant. (3) Lisibilité : path.join(__dirname, 'data', 'fichier.txt') exprime clairement l'intention (assembler ces segments en chemin). (4) Analyse propre : extraire l'extension, le nom, le dossier proprement (basename/dirname/extname) plutôt qu'avec des split et indexOf fragiles. Un point lié important : utilisez __dirname (le dossier du fichier courant) plutôt que des chemins relatifs « nus ». Pourquoi ? Un chemin relatif comme './data/fichier.txt' est résolu par rapport au RÉPERTOIRE DE TRAVAIL COURANT (d'où on a lancé node), pas par rapport au fichier — ce qui varie et cause des « fichier introuvable » selon d'où on lance le programme. En combinant __dirname (position du fichier) avec path.join, vous obtenez des chemins fiables, ancrés à l'emplacement de votre code, quel que soit le répertoire de lancement. Exemple robuste : path.join(__dirname, 'data', 'config.json') pointe toujours vers le bon fichier, peu importe le système et le répertoire de lancement. La règle à adopter : ne construisez jamais un chemin par concaténation de chaînes ; utilisez toujours path.join (souvent avec __dirname). C'est une habitude simple qui élimine toute une catégorie de bugs (portabilité, séparateurs, chemins relatifs). Elle paraît anodine mais fait la différence entre un code qui marche « chez vous » et un code qui marche PARTOUT — un vrai marqueur de code Node soigné. Prenez ce réflexe dès vos premières manipulations de fichiers.