Datei-Operationen sind eine der Hauptaufgaben im Main-Prozess. Node bringt das fs/promises-Modul mit — modern, async, sauber. Hier die wichtigsten Operationen für Electron-Apps und das Pattern, mit dem der Renderer sicher Datei-Zugriffe anfordert.

Standard-Operationen

JavaScript
import fs from 'node:fs/promises';

// Datei lesen
const text = await fs.readFile('/path/to/file.txt', 'utf-8');
const buf  = await fs.readFile('/path/to/binary.bin');   // Buffer

// Datei schreiben
await fs.writeFile('/path/to/out.txt', 'Hallo Welt');
await fs.writeFile('/path/to/out.json', JSON.stringify(data, null, 2));

// Existenz prüfen
try {
    await fs.access('/path/to/file');
    // existiert
} catch {
    // nicht da
}

// Verzeichnis erstellen (rekursiv)
await fs.mkdir('/path/to/nested/dir', { recursive: true });

// Verzeichnis lesen
const entries = await fs.readdir('/path/to/dir');
const detailed = await fs.readdir('/path/to/dir', { withFileTypes: true });

// Datei löschen
await fs.unlink('/path/to/file');

// Verzeichnis löschen (rekursiv)
await fs.rm('/path/to/dir', { recursive: true });

// Datei-Info
const stat = await fs.stat('/path/to/file');
console.log(stat.size, stat.mtime);

fs/promises ist Node-Standard — die gleichen APIs, die in Server-Apps und CLI-Tools verwendet werden.

Renderer fragt, Main führt aus

Der Renderer hat keinen direkten Zugriff. Pattern: IPC-Bridge, mit Validierung im Main.

JavaScript main.js
import { app, ipcMain } from 'electron';
import fs from 'node:fs/promises';
import path from 'node:path';

const ALLOWED_ROOT = app.getPath('userData');

function isAllowed(p) {
    const resolved = path.resolve(p);
    return resolved.startsWith(ALLOWED_ROOT);
}

ipcMain.handle('files:read', async (_event, p) => {
    if (!isAllowed(p)) throw new Error('Path not allowed');
    return fs.readFile(p, 'utf-8');
});

ipcMain.handle('files:write', async (_event, p, content) => {
    if (!isAllowed(p)) throw new Error('Path not allowed');
    return fs.writeFile(p, content);
});

Pflicht-Punkt: immer Pfad-Validierung. Der Renderer ist nicht trusted — eine kompromittierte Webseite könnte sonst /etc/passwd lesen oder Logout-Skripte schreiben.

Streams für große Dateien

JavaScript
import fs from 'node:fs';   // Stream-API ist im klassischen 'fs', nicht 'fs/promises'

ipcMain.handle('files:read-large', async (event, p) => {
    return new Promise((resolve, reject) => {
        const stream = fs.createReadStream(p, { encoding: 'utf-8' });
        let bytesRead = 0;

        stream.on('data', (chunk) => {
            bytesRead += chunk.length;
            event.sender.send('files:progress', { bytesRead });
        });

        stream.on('end', () => resolve('ok'));
        stream.on('error', reject);
    });
});

Bei mehreren Hundert MB lohnt sich Streaming — nicht das ganze File auf einmal in den Speicher laden. Mit event.sender.send parallel Progress-Events pushen.

File-Watching

JavaScript
import { watch } from 'node:fs';

const watcher = watch('/path/to/dir', { recursive: true }, (event, filename) => {
    console.log(`${event}: ${filename}`);
    // event: 'rename' oder 'change'
    mainWindow.webContents.send('files:changed', { event, filename });
});

// Cleanup
app.on('before-quit', () => {
    watcher.close();
});

Plattform-Unterschiede:

  • macOS/Linux: recursive: true funktioniert (Linux ab Node 20)
  • Windows: recursive: true funktioniert von jeher

Für robustere File-Watcher: chokidar als Bibliothek — abstrahiert Plattform-Eigenheiten.

Atomares Speichern

Wenn deine App eine wichtige Datei speichert (Settings, Dokumente), ist atomares Schreiben Pflicht — sonst riskiert ein Crash mitten im Schreiben eine korrupte Datei.

JavaScript
async function atomicWrite(target, content) {
    const tmp = target + '.tmp.' + process.pid;
    await fs.writeFile(tmp, content);
    await fs.rename(tmp, target);
}

await atomicWrite(configPath, JSON.stringify(settings));

Pattern: erst in eine .tmp-Datei schreiben, dann atomar umbenennen. rename ist auf den meisten Filesystems atomar — falls der Prozess in der Mitte stirbt, ist entweder noch die alte oder schon die neue Datei da, nie eine halbe.

Bibliothek dafür: write-file-atomic — dasselbe Pattern als robust getestetes Modul.

Encoding-Eigenheiten

JavaScript
// Default: Buffer (raw bytes)
const buf = await fs.readFile('/path/file.txt');

// Mit Encoding: String
const str = await fs.readFile('/path/file.txt', 'utf-8');

// Klassische Encodings
await fs.readFile('/path/legacy.txt', 'latin1');     // Windows-1252-artig
await fs.readFile('/path/legacy.txt', 'utf16le');     // UTF-16 LE

// BOM beachten — Node entfernt es nicht automatisch
if (str.charCodeAt(0) === 0xfeff) {
    return str.slice(1);   // BOM weg
}

UTF-8 ist heute Standard. Aber bei Windows-spezifischen Tools landest du manchmal in UTF-16 LE oder ANSI — dann explizit Encoding angeben.

Interessantes

fs/promises ist der moderne Default.

import fs from 'node:fs/promises' — alles async, Promise-based. Das alte Callback-API (fs.readFile(path, cb)) gibt's noch, ist aber für neue Apps nicht mehr empfohlen.

Pfad-Validation NICHT im Preload — IMMER im Main.

Renderer-Inputs sind nicht trusted. Validation gehört in den Main-Handler, vor jedem fs-Aufruf. path.resolve plus startsWith(ALLOWED_ROOT) ist das Standard-Pattern.

Atomares Schreiben für wichtige Dateien.

Settings, Dokumente, Konfig — niemals direkt mit writeFile über die alte Datei. Erst .tmp, dann rename. Schützt vor Korruption bei Crash mitten im Schreiben.

Streams für große Dateien — sonst Memory-Spike.

readFile von 1 GB lädt 1 GB in RAM. Mit createReadStream chunkweise — Memory bleibt klein. Bei jedem File-Operation über ~50 MB lohnt's sich.

fs.watch ist plattformabhängig.

Auf Linux war recursive: true lange unpraktikabel. Ab Node 20 funktioniert's, aber mit Performance-Charakteristiken. Für robustes File-Watching plattformübergreifend: chokidar-Library.

Default-Encoding ist Buffer, nicht String.

Ohne Encoding-Argument bekommst du einen Buffer. Beim Logging oder direkten String-Operations: 'utf-8' mitgeben. Sonst: explizit mit .toString('utf-8') konvertieren.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Native APIs & Filesystem

Zur Übersicht