Zum Inhalt springen
aviral gupta

// B3.1 · ca. 38 Min. · Einstieg

Pfade und Datei-URLs

Nach dieser Lektion bauen Sie Pfade, die auf jedem System und aus jedem Ordner funktionieren, und wandeln Pfade in Datei-URLs um und zurück.

Lektion 1 von 5 in B3 Dateien und Pfade

Anfang des Moduls

Danach können Sie

  • Pfade mit node:path bauen und zerlegen und mit path.posix oder path.win32 überall dasselbe Ergebnis bekommen
  • process.cwd() von import.meta.dirname unterscheiden und Dateien neben einem Modul aus jedem Ordner erreichen
  • Datei-URLs nutzen: new URL mit import.meta.url, fileURLToPath und pathToFileURL
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus B2.4: Welche Aussagen über ein ES-Modul stimmen? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es voraus, bevor Sie weiterlesen. Sie sind in /home/ada und führen node shop/src/main.js aus. Was gibt es aus?

    // /home/ada/shop/src/main.js
    import path from "node:path";
    console.log(path.resolve("data.json"));
    
  3. Üben · Aufgabe 3 von 7

    Setzen Sie die Funktion ein, sodass die Zeile notes.txt liefert, wo immer Sie sie ausführen.

    path.posix.____("docs", "..", "notes.txt") // "notes.txt"
    path.posix.("docs", "..", "notes.txt")
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Aufruf mit "/home/ada/notes/todo.txt" zu, was er liefert.

  5. Üben · Aufgabe 5 von 7

    Ein Windows-Pfad kommt in einem Programm an, das unter Linux läuft und path.posix nutzt. Was gibt es aus?

    import path from "node:path";
    console.log(path.posix.basename("C:\\temp\\myfile.html"));
    
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Ein Nutzer gibt für einen Upload den Dateinamen "/etc/passwd" ein. Was geben die beiden Zeilen aus?

    import path from "node:path";
    const name = "/etc/passwd"; // typed by a user
    console.log(path.posix.join("/srv/uploads", name));
    console.log(path.posix.resolve("/srv/uploads", name));
    
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe, lokal. Legen Sie in einem Ordner shop mit "type": "module" die Datei data/prices.json mit {"tea": 2.5} an und schreiben Sie src/main.js. Es baut den Pfad von data/prices.json aus import.meta.dirname, liest und parst die Datei und gibt den Pfad, seine Datei-URL (pathToFileURL) und den Preis von tea aus. Starten Sie es in shop mit node src/main.js, dann aus einem anderen Ordner mit dem vollen Pfad zu main.js: Die Ausgabe muss gleich sein.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Pfade für eine Notiz-App

main.js arbeitet auf drei Arten mit Pfaden: reine String-Arbeit mit path.posix und path.win32, der eigene Ort des Moduls mit import.meta und eine Datei daneben, gefunden über Pfad und über URL. data.json liegt neben main.js. Führen Sie node main.js aus; die Ausgabe ist aus jedem Ordner gleich.

main.js

// Paths for a notes app: string work with node:path, then the module's own place.
import path from "node:path";
import {readFileSync} from "node:fs";
import {fileURLToPath, pathToFileURL} from "node:url";

// 1. String work. path.posix gives the same result on every system.
const note = "/home/ada/notes/2026/todo.txt";
console.log(path.posix.dirname(note));
console.log(path.posix.parse(note).name, path.posix.extname(note));
console.log(path.posix.join("/home/ada/notes", "..", "photos", "cat.png"));
console.log(path.posix.relative("/home/ada/notes", note));
console.log(path.win32.join("C:\\Users\\ada", "notes", "todo.txt"));

// 2. Where is this module? The same from any folder you run node in.
console.log(path.basename(import.meta.filename));
console.log(import.meta.filename === fileURLToPath(import.meta.url));
console.log(import.meta.dirname === path.dirname(import.meta.filename));

// 3. Two ways to the file next to this module.
const byPath = path.join(import.meta.dirname, "data.json");
const byUrl = new URL("./data.json", import.meta.url);
console.log(byUrl.protocol, fileURLToPath(byUrl) === byPath);
console.log(JSON.parse(readFileSync(byUrl, "utf8")).title);

// 4. In a URL, a space becomes %20.
console.log(pathToFileURL("/home/ada/my notes/todo.txt", {windows: false}).href);

data.json

{"title": "My notes"}

Ausführen mit

node main.js

Ausgabe

/home/ada/notes/2026
todo .txt
/home/ada/photos/cat.png
2026/todo.txt
C:\Users\ada\notes\todo.txt
main.js
true
true
file: true
My notes
file:///home/ada/my%20notes/todo.txt
  • path.posix und path.win32 berechnen unter Linux, macOS und Windows dieselben Strings, also ändern sich diese Zeilen nie.
  • import.meta.filename ist fileURLToPath(import.meta.url), und import.meta.dirname ist dessen dirname: drei Blicke auf einen Ort.
  • readFileSync nimmt das URL-Objekt direkt an; der mit join gebaute Pfad führt zur selben Datei.
  • {windows: false} verlangt einen POSIX-Pfad, also ist die URL auch unter Windows dieselbe.

Übungen

Übung 1 von 3

Ihr eigenes extname

Schreiben Sie extname(p) für POSIX-Pfade, wie path.posix.extname es macht: Nehmen Sie den letzten Teil nach dem letzten / und geben Sie alles ab seinem letzten Punkt zurück. Hat dieser Teil keinen Punkt, oder ist sein einziger Punkt das erste Zeichen (".env"), geben Sie "" zurück. "archive.tar.gz" ergibt ".gz", "index." ergibt ".". Reine String-Arbeit, läuft also auch im Browser.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    p.lastIndexOf("/") findet das letzte Trennzeichen; slice danach liefert den letzten Teil.

  2. Hinweis 2

    In diesem Teil findet lastIndexOf(".") den letzten Punkt.

  3. Hinweis 3

    Nur ein Punkt nach Position 0 beginnt eine Endung: return dot > 0 ? base.slice(dot) : "".

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// The extension of the last part of a POSIX path: "todo.txt" -> ".txt".
export function extname(p) {
  const base = p.slice(p.lastIndexOf("/") + 1);
  const dot = base.lastIndexOf(".");
  return dot > 0 ? base.slice(dot) : "";
}

console.log(extname("/home/ada/notes/todo.txt"));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// The extension of the last part of a POSIX path: "todo.txt" -> ".txt".
export function extname(p) {
  return p.slice(p.indexOf("."));
}

console.log(extname("/home/ada/notes/todo.txt"));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {extname} from './main.js';

const check = (p, want) => assert.equal(extname(p), want, `extname(${JSON.stringify(p)}) lieferte ${JSON.stringify(extname(p))}, erwartet ${JSON.stringify(want)}`);

test('ein einfacher Dateiname', () => {
  check('todo.txt', '.txt');
  check('/home/ada/notes/todo.txt', '.txt');
});

test('nur der letzte Punkt zählt', () => {
  check('archive.tar.gz', '.gz');
});

test('ein Punkt im Ordnernamen zählt nicht', () => {
  check('/home/ada/v1.2/notes', '');
});

test('keine Endung: kein Punkt oder nur ein Punkt am Anfang', () => {
  check('README', '');
  check('/home/ada/.env', '');
});

test('ein Punkt am Ende ist eine eigene Endung', () => {
  check('index.', '.');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 3

Ihr eigenes POSIX-join

Schreiben Sie joinPosix(...parts) wie path.posix.join: Leere Teile fallen weg, der Rest wird mit / verbunden und dann aufgeräumt. Mehrere Schrägstriche werden einer, "."-Teile fallen weg, ".." entfernt den Teil davor oder bleibt am Anfang eines relativen Pfads stehen; an der Wurzel bleibt ".." an der Wurzel. Ein Pfad, der mit / beginnt, bleibt absolut, und ein leeres Ergebnis ist ".". Werfen Sie einen TypeError, wenn ein Teil kein String ist. Einen Schrägstrich ganz am Ende dürfen Sie ignorieren. Reine String-Arbeit, läuft also auch im Browser.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Prüfen Sie zuerst jeden Teil; dann ergibt parts.filter((part) => part !== "").join("/") einen String.

  2. Hinweis 2

    Teilen Sie ihn an "/" und gehen Sie die Segmente durch: "" und "." überspringen, bei ".." das letzte behaltene Segment mit pop entfernen.

  3. Hinweis 3

    Merken Sie sich, ob der String mit / begann. Behalten Sie ".." nur in einem relativen Pfad ohne etwas zum Entfernen, und geben Sie "." zurück, wenn nichts übrig ist.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// Joins POSIX path parts and tidies the result, like path.posix.join.
export function joinPosix(...parts) {
  for (const part of parts) {
    if (typeof part !== "string") throw new TypeError("every part must be a string, got " + typeof part);
  }
  const joined = parts.filter((part) => part !== "").join("/");
  if (joined === "") return ".";
  const absolute = joined.startsWith("/");
  const kept = [];
  for (const segment of joined.split("/")) {
    if (segment === "" || segment === ".") continue;
    if (segment === "..") {
      if (kept.length > 0 && kept.at(-1) !== "..") kept.pop();
      else if (!absolute) kept.push("..");
    } else {
      kept.push(segment);
    }
  }
  const body = kept.join("/");
  return absolute ? "/" + body : body || ".";
}

console.log(joinPosix("docs", "..", "notes.txt"));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// Joins POSIX path parts and tidies the result, like path.posix.join.
export function joinPosix(...parts) {
  return parts.join("/");
}

console.log(joinPosix("docs", "..", "notes.txt"));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {joinPosix} from './main.js';

const check = (parts, want) => {
  const got = joinPosix(...parts);
  assert.equal(got, want, `joinPosix(${parts.map((p) => JSON.stringify(p)).join(', ')}) lieferte ${JSON.stringify(got)}, erwartet ${JSON.stringify(want)}`);
};

test('Teile werden mit / verbunden', () => {
  check(['/home/ada', 'notes', 'todo.txt'], '/home/ada/notes/todo.txt');
});

test('.. entfernt den Teil davor', () => {
  check(['docs', '..', 'notes.txt'], 'notes.txt');
  check(['/home/ada/shop/src', '..', 'data', 'prices.json'], '/home/ada/shop/data/prices.json');
});

test('mehrere Schrägstriche und .-Teile werden aufgeräumt', () => {
  check(['a//', 'b'], 'a/b');
  check(['./a', './b'], 'a/b');
});

test('ein führender Schrägstrich bei einem späteren Teil wird angehängt', () => {
  check(['/srv/uploads', '/etc/passwd'], '/srv/uploads/etc/passwd');
});

test('.. am Anfang eines relativen Pfads bleibt, an der Wurzel endet es', () => {
  check(['..', 'a'], '../a');
  check(['/', '..', 'etc'], '/etc');
});

test('bleibt nichts übrig, ergibt das .', () => {
  check([], '.');
  check(['a', '..'], '.');
});

test('ein Teil, der kein String ist, wirft einen TypeError', () => {
  assert.throws(() => joinPosix('notes', 2026), TypeError, 'joinPosix("notes", 2026) sollte einen TypeError werfen');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 3 von 3

Dateien neben dem Modul

notes.txt liegt neben main.js. Schreiben Sie drei Funktionen, die aus jedem Ordner funktionieren, in dem Sie node starten. here(name) liefert den absoluten Pfad von name im Ordner von main.js, gebaut mit path.join und import.meta.dirname. hereUrl(name) liefert ein URL-Objekt für dieselbe Datei, gebaut mit new URL und import.meta.url. readHere(name) liest diese Datei über hereUrl als UTF-8-Text. Führen Sie die Tests mit node --test aus.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    path.join(import.meta.dirname, name) liefert den Pfad; join setzt das Trennzeichen dazwischen.

  2. Hinweis 2

    new URL(name, import.meta.url) löst name gegen die URL von main.js auf.

  3. Hinweis 3

    readFileSync nimmt ein URL-Objekt an: readFileSync(hereUrl(name), "utf8").

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import path from "node:path";
import {readFileSync} from "node:fs";

export function here(name) {
  return path.join(import.meta.dirname, name);
}

export function hereUrl(name) {
  return new URL(name, import.meta.url);
}

export function readHere(name) {
  return readFileSync(hereUrl(name), "utf8");
}
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import path from "node:path";
import {readFileSync} from "node:fs";

export function here(name) {
  return name;
}

export function hereUrl(name) {
  return new URL(name);
}

export function readHere(name) {
  return readFileSync(name, "utf8");
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {here, hereUrl, readHere} from './main.js';

test('here() ist die Datei im Ordner von main.js', () => {
  const got = here('notes.txt');
  const folder = import.meta.dirname;
  assert.ok(got.startsWith(folder) && got.endsWith('notes.txt') && got.length === folder.length + 'notes.txt'.length + 1, `here('notes.txt') lieferte ${JSON.stringify(got)}; erwartet war notes.txt in ${folder}`);
});

test('hereUrl() ist eine file:-URL neben main.js', () => {
  const got = hereUrl('notes.txt');
  assert.ok(got instanceof URL, 'hereUrl() sollte ein URL-Objekt liefern');
  assert.equal(got.href, new URL('./notes.txt', import.meta.url).href, `hereUrl('notes.txt') lieferte ${got.href}`);
});

test('readHere() liest den Text von notes.txt', () => {
  assert.equal(readHere('notes.txt'), 'buy tea\ncall Ben\n', `readHere('notes.txt') lieferte ${JSON.stringify(readHere('notes.txt'))}`);
});

notes.txt

buy tea
call Ben

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Einen Pfad mit + zusammenkleben

// The folder as import.meta.dirname gives it: no slash at the end.
import {readFileSync} from "node:fs";

const folder = "/home/ada/shop/src";
console.log(readFileSync(folder + "notes.txt", "utf8"));

Was Node.js ausgibt

Error: ENOENT: no such file or directory, open '/home/ada/shop/srcnotes.txt'

Warum, und die Lösung

import.meta.dirname hat am Ende keinen Schrägstrich, also klebt + die Namen zusammen, und die Datei wird unter einem Namen gesucht, den es nicht gibt. path.join(import.meta.dirname, "notes.txt") setzt das Trennzeichen des Systems ein und verdoppelt auch keines.

Eine Zahl als Pfadteil

import path from "node:path";

const year = 2026;
console.log(path.join("notes", year, "todo.txt"));

Was Node.js ausgibt

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type number (2026)

Warum, und die Lösung

Jeder Teil von path.join muss ein String sein; node:path wandelt Zahlen nicht für Sie um. Schreiben Sie String(year) oder ein Template-Literal: path.join("notes", `${year}`, "todo.txt").

Ein Ordnerpfad als Basis einer URL

// Looks for data.json next to this module, but with the wrong base.
const url = new URL("data.json", import.meta.dirname);
console.log(url.href);

Was Node.js ausgibt

TypeError: Invalid URL

Warum, und die Lösung

Die Basis von new URL muss selbst eine URL sein, und import.meta.dirname ist ein Pfad wie /home/ada/shop/src. Nehmen Sie import.meta.url als Basis: new URL("data.json", import.meta.url). Oder bleiben Sie bei Pfaden: path.join(import.meta.dirname, "data.json").

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

node:path ist Arbeit mit Strings

path.join("docs", "..", "notes.txt") fügt die Teile mit dem Trennzeichen zusammen und räumt das Ergebnis auf: notes.txt. path.resolve arbeitet von rechts nach links, bis ein absoluter Pfad entsteht, und nimmt den aktuellen Ordner, wenn keiner angegeben ist. dirname, basename, extname und parse zerlegen einen Pfad; relative liefert den Weg von einem Pfad zum anderen. Keine davon schaut auf die Festplatte: Ein Pfad zu einer fehlenden Datei wird genauso berechnet. Standardmäßig folgt node:path dem System, auf dem es läuft: / unter Linux und macOS, \ unter Windows (path.sep). path.posix und path.win32 liefern auf jedem System dasselbe Ergebnis; das wollen Sie für Tests und für Pfade, die von anderswo kommen.

Relativ wozu?

Ein relativer Pfad wie "data/prices.json", an path.resolve oder node:fs übergeben, wird gegen process.cwd() aufgelöst: den Ordner, in dem Sie node gestartet haben, nicht den Ordner der Datei. Starten Sie dasselbe Skript aus einem anderen Ordner, sucht es woanders. import.meta.dirname ist der Ordner des Moduls selbst, import.meta.filename sein vollständiger Pfad; beide bleiben gleich, wo immer Sie node starten. Eine Datei, die zu Ihrem Code gehört, finden Sie also mit path.join(import.meta.dirname, "..", "data", "prices.json"). Bauen Sie Pfade mit join, nicht mit +: import.meta.dirname hat am Ende keinen Schrägstrich, also ergibt dirname + "notes.txt" ...srcnotes.txt.

Datei-URLs

import.meta.url ist der Ort des Moduls als file:-URL, etwa file:///home/ada/shop/src/main.js. new URL("./data.json", import.meta.url) löst einen Namen dagegen auf, und node:fs nimmt das URL-Objekt als Pfad an. Eine URL ist kein Pfad: Ein Leerzeichen steht darin als %20, und unter Windows beginnt sie mit file:///C:/. fileURLToPath(url) macht daraus einen echten Pfad für dieses System und dekodiert %20; url.pathname tut beides nicht. pathToFileURL(path) geht den umgekehrten Weg und kodiert, was eine URL braucht. new URL braucht als Basis eine URL, also wirft new URL("data.json", import.meta.dirname) TypeError: Invalid URL.

Quellen

Zuletzt geprüft am 3. Oktober 2026