JavaScript ist seit langem eine der beliebtesten Skriptsprachen, aber für die serverseitige Entwicklung von Backend-Anwendungen war sie lange Zeit nicht die beste Wahl. Dann kam Node.js auf, mit dem sich serverseitige, ereignisgesteuerte, leichtgewichtige Anwendungen erstellen lassen, die auf der Programmiersprache JavaScript basieren.

Node.js ist eine Open-Source-JavaScript-Laufzeitumgebung, die kostenlos heruntergeladen und auf allen gängigen Betriebssystemen (Windows, Mac, Linux) installiert werden kann. Node.js ist in den letzten Jahren bei App-Entwicklern immer beliebter geworden und hat viele neue Beschäftigungsmöglichkeiten für JavaScript-Entwickler/innen geschaffen, die eine Spezialisierung suchen.

In diesem Artikel werden wir lernen, wie man das Dateisystem mit Node.js verwaltet. Mit den Node.js-APIs kannst du mühelos mit dem Dateisystem interagieren und viele komplexe Operationen durchführen. Wenn du weißt, wie du damit umgehst, wird deine Produktivität steigen.

Lass uns eintauchen!

Voraussetzungen für das Verständnis des Node.js-Dateisystems

Die wichtigste Voraussetzung ist die Installation von Node.js auf deinem Betriebssystem. Da Node.js keine komplexe Hardware benötigt, kannst du Node.js einfach herunterladen und auf deinem Computer installieren.

Um mit Node.js-Modulen wie dem Dateisystem (auch „FS“ oder „fs“ genannt) arbeiten zu können, ist es hilfreich, wenn du auch Grundkenntnisse in JavaScript hast. Ein grundlegendes Verständnis von JavaScript-Funktionen, Callback-Funktionen und Promises wird dir helfen, dieses Thema noch schneller in den Griff zu bekommen.

Node.js Dateisystem-Modul

Die Arbeit mit Dateien und Verzeichnissen gehört zu den Grundbedürfnissen einer Full-Stack-Anwendung. Deine Nutzer/innen wollen vielleicht Bilder, Lebensläufe oder andere Dateien auf einen Server hochladen. Gleichzeitig muss deine Anwendung vielleicht Konfigurationsdateien lesen, Dateien verschieben oder sogar ihre Berechtigungen programmatisch ändern.

Das Node.js-Dateisystemmodul deckt all diese Anforderungen ab. Es bietet mehrere APIs, um nahtlos mit den Dateisystemen zu interagieren. Die meisten APIs sind mit Optionen und Flags anpassbar. Außerdem kannst du mit ihnen sowohl synchrone als auch asynchrone Dateioperationen durchführen.

Bevor wir in das Dateisystemmodul eintauchen, werfen wir einen Blick darauf, was es mit dem Node.js-Modul auf sich hat.

Node.js-Module

Node.js-Module sind eine Reihe von Funktionen, die als APIs zur Verfügung stehen und von einem Verbraucherprogramm genutzt werden können. So gibt es zum Beispiel das Modul fs, das mit dem Dateisystem interagiert. Ebenso nutzt ein http Modul seine Funktionen, um einen Server zu erstellen und viele weitere Operationen auszuführen. Node.js bietet eine Vielzahl von Modulen, die viele Low-Level-Funktionen für dich abstrahieren.

Du kannst auch deine eigenen Module erstellen. Ab Node.js Version 14 kannst du Node.js-Module auf zwei Arten erstellen und verwenden: CommonJS (CJS) und ESM (MJS) Module. Alle Beispiele, die wir in diesem Artikel sehen werden, sind im CJS-Stil gehalten.

Arbeiten mit Dateien in Node.js

Die Arbeit mit Dateien umfasst verschiedene Operationen mit Dateien und Verzeichnissen (Ordnern). Jetzt werden wir jeden dieser Vorgänge anhand von Beispielen im Quellcode kennenlernen. Bitte öffne deinen bevorzugten Quellcode-Editor und probiere sie aus, während du mitliest.

Importiere zunächst das Modul fs in deine Quelldatei, um mit den Methoden des Dateisystems arbeiten zu können. Im CJS-Stil verwenden wir die Methode require, um eine Methode aus einem Modul zu importieren. Um also die Methoden des Moduls fs zu importieren und zu verwenden, musst du Folgendes tun:

const { writeFile } = require('fs/promises');

Beachte auch, dass wir die Methode writeFile aus dem Paket fs/promises importieren. Wir wollen die promisified-Methoden verwenden, weil sie die neuesten sind und weil sie mit den async/await-Schlüsselwörtern und weniger Code einfach zu verwenden sind. Andere Alternativen sind die synchronen Methoden und die Callback-Funktionen, die wir später sehen werden.

Wie man eine Datei erstellt und in eine Datei schreibt

Du kannst eine Datei auf drei Arten erstellen und in sie schreiben:

  1. Verwendung der Methode writeFile
  2. Verwendung der appendFile Methode
  3. Die Methode openFile verwenden

Diese Methoden akzeptieren einen Dateipfad und die Daten als Inhalt, die in die Datei geschrieben werden sollen. Wenn die Datei existiert, ersetzen sie den Inhalt in der Datei. Andernfalls erstellen sie eine neue Datei mit dem Inhalt.

1. Verwendung der Methode writeFile

Der folgende Codeschnipsel zeigt die Verwendung der Methode writeFile. Beginne damit, eine Datei mit dem Namen createFile.js zu erstellen, indem du das folgende Snippet verwendest:

const { writeFile } = require('fs/promises');
async function writeToFile(fileName, data) {
  try {
    await writeFile(fileName, data);
    console.log(`Wrote data to ${fileName}`);
  } catch (error) {
    console.error(`Got an error trying to write the file: ${error.message}`);
  }
}

Beachte, dass wir das Schlüsselwort await verwenden, um die Methode aufzurufen, da sie ein JavaScript-Versprechen zurückgibt. Ein erfolgreiches Versprechen erstellt/schreibt die Datei. Wir haben einen try-catch-Block, um Fehler zu behandeln, falls das Versprechen abgelehnt wird.

Jetzt können wir die Funktion writeToFile aufrufen:

writeToFile('friends.txt', 'Bob');

Als Nächstes öffnest du eine Eingabeaufforderung oder ein Terminal und führst das obige Programm mit folgendem Befehl aus:

node createFile.js

Es erstellt eine neue Datei namens friends.txt mit einer Zeile, in der einfach steht:

Bob

2. Verwenden der appendFile-Methode

Wie der Name schon sagt, wird diese Methode hauptsächlich zum Anhängen und Bearbeiten einer Datei verwendet. Du kannst dieselbe Methode aber auch verwenden, um eine Datei zu erstellen.

Sieh dir die folgende Funktion an. Wir verwenden die Methode appendFile mit dem Flag w, um eine Datei zu schreiben. Das Standardflag für das Anhängen an eine Datei ist a:

const { appendFile} = require('fs/promises');

async function appendToFile(fileName, data) {
  try {
    await appendFile(fileName, data, { flag: 'w' });
    console.log(`Appended data to ${fileName}`);
  } catch (error) {
    console.error(`Got an error trying to append the file: {error.message}`);
  }
}

Jetzt kannst du die obige Funktion wie folgt aufrufen:

appendToFile('activities.txt', 'Skiing');

Als Nächstes kannst du den Code in der Node.js-Umgebung mit dem Befehl node ausführen, wie wir bereits gesehen haben. Dadurch wird eine Datei namens activities.txt mit dem Inhalt Skiing erstellt.

3. Die Methode open verwenden

Die letzte Methode, die wir lernen werden, um eine Datei zu erstellen und in sie zu schreiben, ist die open Methode. Du kannst eine Datei mit dem Flag w (für „write“) öffnen:

const { open} = require('fs/promises');

async function openFile(fileName, data) {
  try {
    const file = await open(fileName, 'w');
    await file.write(data);
    console.log(`Opened file ${fileName}`);
  } catch (error) {
    console.error(`Got an error trying to open the file: {error.message}`);
  }
}

Rufe nun die Funktion openFile mit auf:

openFile('tasks.txt', 'Do homework');

Wenn du das Skript mit dem Befehl node ausführst, wird eine Datei namens tasks.txt mit dem ursprünglichen Inhalt erstellt:

Do homework

Wie man eine Datei liest

Nachdem wir nun wissen, wie man eine Datei erstellt und in sie schreibt, wollen wir nun lernen, wie man den Inhalt einer Datei liest. Dazu verwenden wir die Methode readFile aus dem Modul Dateisystem.

Erstelle eine Datei namens readThisFile.js mit dem folgenden Code:

// readThisFile.js
const { readFile } = require('fs/promises');
async function readThisFile(filePath) {
  try {
    const data = await readFile(filePath);
    console.log(data.toString());
  } catch (error) {
    console.error(`Got an error trying to read the file: {error.message}`);
 }
}

Jetzt lesen wir alle drei Dateien, die wir erstellt haben, indem wir die Funktion readThisFile aufrufen:

readThisFile('activities.txt');
readThisFile('friends.txt');
readThisFile('tasks.txt');

Zum Schluss führst du das Skript mit dem folgenden Node-Befehl aus:

node readThisFile.js

Du solltest die folgende Ausgabe in der Konsole sehen:

Skiing
Do homework
Bob

Ein Punkt, den du hier beachten solltest: Die Methode readFile liest die Datei asynchron. Das bedeutet, dass die Reihenfolge, in der du die Datei liest, und die Reihenfolge, in der du eine Antwort in der Konsole ausgeben lässt, nicht unbedingt übereinstimmen. Du musst die synchrone Version der Methode readFile verwenden, um die Reihenfolge einzuhalten. Das werden wir hier gleich sehen.

Wie man eine Datei umbenennt

Um eine Datei umzubenennen, verwendest du die Methode rename aus dem fs-Modul. Erstellen wir eine Datei namens rename-me.txt. Wir werden diese Datei programmatisch umbenennen.

Erstelle eine Datei namens renameFile.js mit dem folgenden Code:

const { rename } = require('fs/promises');

async function renameFile(from, to) {
  try {
    await rename(from, to);
    console.log(`Renamed ${from} to ${to}`);
  } catch (error) {
    console.error(`Got an error trying to rename the file: ${error.message}`);
  }
}

Wie du vielleicht bemerkt hast, benötigt die Methode rename zwei Argumente. Das eine ist die Datei mit dem Quellnamen und das andere ist der Zielname.

Rufen wir nun die obige Funktion auf, um die Datei umzubenennen:

const oldName = "rename-me.txt";
const newName = "renamed.txt";
renameFile(oldName, newName);

Wie zuvor führst du die Skriptdatei mit dem Befehl node aus, um die Datei umzubenennen:

node renameFile.js

Wie man eine Datei verschiebt

Das Verschieben einer Datei von einem Verzeichnis in ein anderes ist ähnlich wie das Umbenennen des Pfades. Wir können also auch die Methode rename verwenden, um Dateien zu verschieben.

Legen wir zwei Ordner an, von und nach. Dann erstellen wir eine Datei namens move-me.txt im from-Ordner.

Als Nächstes schreiben wir den Code zum Verschieben der Datei move-me.txt. Erstelle eine Datei namens moveFile.js mit folgendem Codeausschnitt:

const { rename } = require('fs/promises');
const { join } = require('path');
async function moveFile(from, to) {
  try {
    await rename(from, to);
    console.log(`Moved ${from} to ${to}`);
  } catch (error) {
    console.error(`Got an error trying to move the file: ${error.message}`);
  }
}

Wie du sehen kannst, verwenden wir die Methode rename genau wie zuvor. Aber warum müssen wir die Methode join aus dem Modul path importieren (ja, der Pfad ist ein weiteres wichtiges Modul von Node.js)?

Die Methode join wird verwendet, um mehrere angegebene Pfadsegmente zu einem Pfad zu verbinden. Wir werden sie verwenden, um den Pfad von Quell- und Zieldatei zu bilden:

const fromPath = join(__dirname, "from", "move-me.txt");
const destPath = join(__dirname, "to", "move-me.txt");
moveFile(fromPath, destPath);

Und das war’s! Wenn du das Skript moveFile.js ausführst, wirst du sehen, dass die Datei move-me.txt in den Ordner to verschoben wurde.

Wie man eine Datei kopiert

Wir verwenden die Methode copyFile aus dem Modul fs, um eine Datei von der Quelle zum Ziel zu kopieren.

Sieh dir den folgenden Codeschnipsel an:

const { copyFile } = require('fs/promises');
const { join } = require('path');
async function copyAFile(from, to) {
  try {
    await copyFile(from, to);
    console.log(`Copied ${from} to ${to}`);
  } catch (err) {
    console.error(`Got an error trying to copy the file: ${err.message}`);
  }
}

Jetzt kannst du die obige Funktion mit aufrufen:

copyAFile('friends.txt', 'friends-copy.txt');

Sie kopiert den Inhalt der friends.txt in die Datei friends-copy.txt.

Das ist toll, aber wie kopiert man mehrere Dateien?

Du kannst die Promise.all API verwenden, um mehrere Versprechen parallel auszuführen:

async function copyAll(fromDir, toDir, filePaths) {
  return Promise.all(filePaths.map(filePath => {
   return copyAFile(join(fromDir, filePath), join(toDir, filePath));
  }));
}

Jetzt kannst du alle Dateipfade angeben, die von einem Verzeichnis in ein anderes kopiert werden sollen:

copyFiles('from', 'to', ['copyA.txt', 'copyB.txt']);

Du kannst diesen Ansatz auch nutzen, um andere Operationen wie das Verschieben, Schreiben und Lesen von Dateien parallel durchzuführen zu können.

Wie man eine Datei löscht

Wir verwenden die Methode unlink, um eine Datei zu löschen:

const { unlink } = require('fs/promises');
async function deleteFile(filePath) {
  try {
    await unlink(filePath);
    console.log(`Deleted ${filePath}`);
  } catch (error) {
    console.error(`Got an error trying to delete the file: ${error.message}`);
  }
}

Denke daran, dass du den Pfad zu der Datei angeben musst, um sie zu löschen:

deleteFile('delete-me.txt');

Wenn es sich bei dem Pfad um einen Symlink zu einer anderen Datei handelt, hebt die unlink-Methode den Symlink auf, aber die ursprüngliche Datei bleibt unangetastet. Wir werden später noch mehr über Symlinks sprechen.

So änderst du Dateiberechtigungen und -eigentum

Es kann vorkommen, dass du die Dateiberechtigungen programmgesteuert ändern möchtest. Das kann sehr hilfreich sein, um eine Datei schreibgeschützt oder vollständig zugänglich zu machen.

Wir werden die Methode chmod verwenden, um die Berechtigung einer Datei zu ändern:

const { chmod } = require('fs/promises');
async function changePermission(filePath, permission) {
  try {
    await chmod(filePath, permission);
    console.log(`Changed permission to ${permission} for ${filePath}`);
  } catch (error) {
    console.error(`Got an error trying to change permission: ${error.message}`);
  }
}

Wir können den Dateipfad und die Erlaubnis-Bitmaske übergeben, um die Erlaubnis zu ändern.

Hier ist der Funktionsaufruf, um die Berechtigung einer Datei auf schreibgeschützt zu ändern:

changePermission('permission.txt', 0o400);

Ähnlich wie die Berechtigung kannst du auch den Eigentümer einer Datei programmatisch ändern. Wir verwenden dazu die Methode chown:

const { chown } = require('fs/promises');

async function changeOwnership(filePath, userId, groupId) {
  try {
    await chown(filePath, userId, groupId);
    console.log(`Changed ownership to ${userId}:${groupId} for ${filePath}`);
  } catch (error) {
    console.error(`Got an error trying to change ownership: ${error.message}`);
  }
}

Dann rufen wir die Funktion mit dem Dateipfad, der Benutzer-ID und der Gruppen-ID auf:

changeOwnership('ownership.txt', 1000, 1010);

Wie man einen Symlink erstellt

Der symbolische Link (auch Symlink genannt) ist ein Konzept des Dateisystems, um einen Link zu einer Datei oder einem Ordner zu erstellen. Wir erstellen Symlinks, um Verknüpfungen zu einer Zieldatei/einem Zielordner im Dateisystem zu erstellen. Das Node.js-Modul filesystem bietet die Methode symlink, um einen symbolischen Link zu erstellen.

Um den Symlink zu erstellen, müssen wir den Pfad der Zieldatei, den aktuellen Dateipfad und den Typ übergeben:

const { symlink } = require('fs/promises');
const { join } = require('path');
async function createSymlink(target, path, type) {
  try {
    await symlink(target, path, type);
    console.log(`Created symlink to ${target} at ${path}`);
  } catch (error) {
    console.error(`Got an error trying to create the symlink: ${error.message}`);
  }
}

Wir können die Funktion mit aufrufen:

createSymlink('join(__dirname, from, symMe.txt)', 'symToFile', 'file');

Hier haben wir einen Symlink namens symToFile erstellt.

Wie man Änderungen an einer Datei beobachtet

Wusstest du, dass du die Änderungen an einer Datei beobachten kannst? Das ist eine großartige Möglichkeit, um Änderungen und Ereignisse zu überwachen, besonders wenn du sie nicht erwartest. Du kannst sie aufzeichnen und für spätere Überprüfungen aufzeichnen.

Die Methode watch ist die beste Methode, um Dateiveränderungen zu beobachten. Es gibt eine alternative Methode namens watchFile, aber sie ist nicht so leistungsfähig wie die Methode watch.

Bisher haben wir die Methode des Dateisystemmoduls mit den Schlüsselwörtern async/await verwendet. In diesem Beispiel werden wir sehen, wie die Callback-Funktion verwendet wird.

Die Methode watch nimmt den Dateipfad und eine Callback-Funktion als Argumente entgegen. Immer, wenn eine Aktivität in der Datei stattfindet, wird die Funktion callback aufgerufen.

Wir können den Parameter event nutzen, um weitere Informationen über die Aktivitäten zu erhalten:

const fs = require('fs');
function watchAFile(file) {
  fs.watch(file, (event, filename) => {
    console.log(`${filename} file Changed`);
  });
}

Rufe die Funktion auf, indem du einen Dateinamen an watch übergibst:

watchAFile('friends.txt');

Jetzt erfassen wir automatisch alle Aktivitäten in der Datei friends.txt.

Arbeiten mit Verzeichnissen (Ordnern) in Node.js

Jetzt wollen wir rausfinden, wie wir mit Verzeichnissen oder Ordnern arbeiten können. Viele der Operationen wie Umbenennen, Verschieben und Kopieren ähneln den Operationen, die wir für Dateien kennen. Bestimmte Methoden und Operationen sind jedoch nur für Verzeichnisse anwendbar.

Wie man ein Verzeichnis erstellt

Wir verwenden die Methode mkdir, um ein Verzeichnis zu erstellen. Du musst den Verzeichnisnamen als Argument übergeben:

const { mkdir } = require('fs/promises');
async function createDirectory(path) {
  try {
    await mkdir(path);
    console.log(`Created directory ${path}`);
  } catch (error) {
    console.error(`Got an error trying to create the directory: ${error.message}`);
  }
}

Jetzt können wir die Funktion createDirectory mit einem Verzeichnispfad aufrufen:

createDirectory('new-directory');

Dadurch wird ein Verzeichnis namens new-directory erstellt.

Wie man ein temporäres Verzeichnis erstellt

Temporäre Verzeichnisse sind keine normalen Verzeichnisse. Sie haben eine besondere Bedeutung für das Betriebssystem. Du kannst ein temporäres Verzeichnis mit der Methode mkdtemp() erstellen.

Lass uns einen temporären Ordner im temporären Verzeichnis deines Betriebssystems erstellen. Die Informationen für den Speicherort des temporären Verzeichnisses erhalten wir von der Methode tmpdir() des Moduls os:

const { mkdtemp } = require('fs/promises');
const { join } = require('path');
const { tmpdir } = require('os');
async function createTemporaryDirectory(fileName) {
  try {
    const tempDirectory = await mkdtemp(join(tmpdir(), fileName));
    console.log(`Created temporary directory ${tempDirectory}`);
  } catch (error) {
    console.error(`Got an error trying to create the temporary directory: ${error.message}`);
  }
}

Rufen wir nun die Funktion mit dem Verzeichnisnamen auf, um es zu erstellen:

createTemporaryDirectory('node-temp-file-');

Beachte, dass Node.js sechs zufällige Zeichen am Ende des erstellten temporären Ordnernamens anhängt, um ihn eindeutig zu halten.

Wie man ein Verzeichnis löscht

Du musst die Methode rmdir() verwenden, um ein Verzeichnis zu löschen:

const { rmdir } = require('fs/promises');
async function deleteDirectory(path) {
  try {
    await rmdir(path);
    console.log(`Deleted directory ${path}`);
  } catch (error) {
    console.error(`Got an error trying to delete the directory: ${error.message}`);
  }
}

Als Nächstes rufst du die oben genannte Funktion auf und übergibst den Pfad des Ordners, den du löschen möchtest:

deleteDirectory('new-directory-renamed');

Synchrone vs. asynchrone APIs

Bis jetzt haben wir viele Beispiele für Dateisystemmethoden gesehen, die alle asynchron verwendet werden. Es kann jedoch sein, dass du einige Vorgänge synchron abwickeln musst.

Ein Beispiel für eine synchrone Operation ist das Lesen mehrerer Dateien nacheinander. Das Modul fs verfügt über eine Methode namens readFileSync(), die dies ermöglicht:

const { readFileSync } = require('fs');
function readFileSynchronously(path) {
  try {
    const data = readFileSync(path);
    console.log(data.toString());
  } catch (error) {
    console.error(error);
  }
}

Beachte, dass die Methode readFileSync() aus dem Paket „fs/promises“ nicht benötigt wird. Das liegt daran, dass die Methode nicht asynchron ist. Du kannst die obige Funktion also mit aufrufen:

readFileSynchronously('activities.txt');
readFileSynchronously('friends.txt');
readFileSynchronously('tasks.txt');

In diesem Fall werden alle oben genannten Dateien in der Reihenfolge gelesen, in der die Funktionen aufgerufen wurden.

Das Node.js-Dateisystemmodul bietet eine synchrone Methode für andere Operationen wie die Leseoperation. Verwende sie mit Bedacht und nur bei Bedarf. Die asynchronen Methoden sind für die parallele Ausführung viel hilfreicher.

Umgang mit Fehlern

Wie jeder Programmierer weiß, musst du mit Fehlern rechnen und darauf vorbereitet sein, sie zu beheben, wenn du eine Datei- oder Verzeichnisoperation ausführst. Was ist, wenn die Datei nicht gefunden wird oder du keine Berechtigung hast, in eine Datei zu schreiben? Es kann (und wird wahrscheinlich) viele Fälle geben, in denen du auf einen Fehler stoßen könntest.

Du solltest deine Methodenaufrufe immer mit einem try-catch-Block umgeben. Wenn ein Fehler auftritt, geht die Kontrolle an den catch-Block über, wo du den Fehler überprüfen und behandeln kannst. Wie du vielleicht in den obigen Beispielen bemerkt hast, haben wir den try-catch-Block für die Behandlung von Fehlern verwendet, auf die wir gestoßen sind.

Zusammenfassung

Lass uns noch einmal die wichtigsten Punkte dieses Tutorials durchgehen:

  • Das Node.js Dateisystem (fs) Modul hat viele Methoden, die dir bei vielen Low-Level Aufgaben helfen.
  • Du kannst verschiedene Dateioperationen wie Erstellen, Schreiben, Umbenennen, Kopieren, Verschieben, Löschen und viele mehr durchführen.
  • Du kannst verschiedene Verzeichnisoperationen durchführen, z. B. ein temporäres Verzeichnis erstellen, verschieben und vieles mehr.
  • Alle Methoden können asynchron mit JavaScript-Versprechen oder Callback-Funktionen aufgerufen werden.
  • Bei Bedarf kannst du die Methoden auch synchron aufrufen.
  • Ziehe die asynchronen Methoden immer den synchronen vor.
  • Behandle Fehler mit einem try-catch-Block, wenn du mit den Methoden interagierst.

Jetzt, wo wir ein bisschen mit dem Node.js-Dateisystem gearbeitet haben, solltest du es gut im Griff haben. Wenn du dein Wissen weiter ausbauen willst, solltest du dich mit den Node.js-Streams beschäftigen, um die Node.js-Module kennenzulernen. Streams sind effiziente Methoden, um Informationen auszutauschen, z. B. Netzwerkaufrufe, Lesen und Schreiben von Dateien und vieles mehr.

Den gesamten Quellcode, der in diesem Artikel verwendet wird, findest du in diesem GitHub Repository.

Planst du, Node.js für dein nächstes Projekt zu verwenden? Lass uns in den Kommentaren wissen, warum du dich dafür entschieden hast.