Electron kombiniert Chromium und Node.js, damit du mit HTML, CSS und JavaScript vollwertige Desktop-Apps bauen kannst. VS Code, Slack und Discord nutzen es bereits, und du kannst das auch! ⚡

Was ist Electron? 🤔

Stell dir vor, du baust eine Webanwendung, aber statt im Browser läuft sie als eigenständige Desktop-App auf Windows, macOS und Linux. Genau das macht Electron möglich. Im Grunde packt Electron einen Chromium-Browser und eine Node.js-Runtime in ein Paket zusammen. Deine App hat also vollen Zugriff auf Web-APIs und auf das Dateisystem, Betriebssystem-Features und native Module.

Das Ganze wurde ursprünglich von GitHub für den Atom-Editor entwickelt und ist mittlerweile das go-to Framework für Cross-Platform Desktop-Apps.

Electron | Build cross-platform desktop apps with JavaScript, HTML, and CSS
Build cross-platform desktop apps with JavaScript, HTML, and CSS. If you can build a website, you can build a desktop app.

Wie funktioniert Electron unter der Haube? ⚙️

Electron basiert auf zwei Kernkomponenten:

  • Chromium, rendert deine UI (HTML/CSS/JS), genau wie im Browser
  • Node.js, gibt dir Zugriff auf das Dateisystem, Netzwerk, native Module und mehr

Jede Electron-App besteht aus zwei Arten von Prozessen:

Main Process 🌱

Der Main Process ist quasi der “Boss”. Er startet die App, erstellt Fenster, verwaltet den App-Lifecycle und hat vollen Node.js-Zugriff. Es gibt pro App genau einen Main Process.

Renderer Process 🎨

Jedes Fenster (BrowserWindow) bekommt seinen eigenen Renderer Process. Das ist im Grunde ein Chromium-Tab. Hier läuft dein Frontend-Code. Aus Sicherheitsgründen sollte der Renderer keinen direkten Node.js-Zugriff haben.

Deine erste Electron-App aufsetzen 🚀

Lass uns direkt loslegen. Erstelle ein neues Projekt:

mkdir my-electron-app && cd my-electron-app
npm init -y
npm install electron --save-dev

Deine package.json sollte so aussehen:

{
  "name": "my-electron-app",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build": "electron-builder"
  },
  "devDependencies": {
    "electron": "^33.0.0",
    "electron-builder": "^25.0.0"
  }
}

Jetzt die main.js, das Herz deiner App:

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });

  win.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow();
    }
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

Beachte die webPreferences: contextIsolation: true und nodeIntegration: false sind extrem wichtig für die Sicherheit. Dazu später mehr.

Electron Documentation
Getting started guides, API references, and tutorials for building Electron apps.

Preload Scripts, Die sichere Brücke 🛡️

Der Preload-Script ist der sichere Vermittler zwischen Main und Renderer Process. Er läuft im Kontext des Renderers, hat aber Zugriff auf einige Node.js-APIs:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
  // Sichere API für den Renderer
  getSystemInfo: () => ipcRenderer.invoke('get-system-info'),
  openFile: () => ipcRenderer.invoke('dialog:openFile'),
  onUpdateAvailable: (callback) => {
    ipcRenderer.on('update-available', (_event, info) => callback(info));
  },
  saveData: (data) => ipcRenderer.invoke('save-data', data)
});

Mit contextBridge.exposeInMainWorld stellst du nur die Funktionen bereit, die der Renderer wirklich braucht. Kein direkter Node.js-Zugriff, keine Sicherheitslücken.

IPC-Kommunikation, Main & Renderer reden miteinander 💬

IPC (Inter-Process Communication) ist das Rückgrat jeder Electron-App. So kommunizieren Main und Renderer Process sicher miteinander:

// main.js - IPC Handler registrieren
const { ipcMain, dialog } = require('electron');
const os = require('os');
const fs = require('fs');

ipcMain.handle('get-system-info', () => {
  return {
    platform: os.platform(),
    arch: os.arch(),
    cpus: os.cpus().length,
    totalMemory: Math.round(os.totalmem() / 1024 / 1024 / 1024) + ' GB',
    nodeVersion: process.versions.node,
    electronVersion: process.versions.electron
  };
});

ipcMain.handle('dialog:openFile', async () => {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ['openFile'],
    filters: [{ name: 'Text Files', extensions: ['txt', 'md', 'json'] }]
  });
  if (canceled) return null;
  return fs.readFileSync(filePaths[0], 'utf-8');
});

ipcMain.handle('save-data', async (_event, data) => {
  const { canceled, filePath } = await dialog.showSaveDialog({
    filters: [{ name: 'JSON', extensions: ['json'] }]
  });
  if (canceled || !filePath) return false;
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
  return true;
});

Und im Renderer nutzt du die exponierte API:

// renderer.js
async function showSystemInfo() {
  const info = await window.electronAPI.getSystemInfo();

  const container = document.getElementById('system-info');
  // Bestehende Inhalte entfernen
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }

  Object.entries(info).forEach(([key, value]) => {
    const p = document.createElement('p');
    const strong = document.createElement('strong');
    strong.textContent = key + ': ';
    p.appendChild(strong);
    p.appendChild(document.createTextNode(String(value)));
    container.appendChild(p);
  });
}

document.getElementById('btn-sysinfo')
  .addEventListener('click', showSystemInfo);

document.getElementById('btn-open')
  .addEventListener('click', async () => {
    const content = await window.electronAPI.openFile();
    if (content) {
      document.getElementById('file-content').textContent = content;
    }
  });

TypeScript nutzen 💪

Electron und TypeScript sind ein Dream-Team. TypeScript gibt dir Autovervollständigung, Typ-Prüfungen und macht deinen Code deutlich robuster. Gerade bei IPC-Kommunikation, wo du Daten zwischen Prozessen hin- und herschickst, sind Typen Gold wert.

Warum du nur noch TypeScript nutzen solltest ☝️
TypeScript bringt Typsicherheit und bessere Entwicklererfahrung in deine JavaScript-Projekte.
// types.ts - Gemeinsame Typen für Main und Renderer
interface SystemInfo {
  platform: string;
  arch: string;
  cpus: number;
  totalMemory: string;
  nodeVersion: string;
  electronVersion: string;
}

interface ElectronAPI {
  getSystemInfo: () => Promise<SystemInfo>;
  openFile: () => Promise<string | null>;
  saveData: (data: unknown) => Promise<boolean>;
  onUpdateAvailable: (callback: (info: UpdateInfo) => void) => void;
}

declare global {
  interface Window {
    electronAPI: ElectronAPI;
  }
}

Packaging mit electron-builder 📦

Wenn deine App fertig ist, willst du sie als installierbare Anwendung verteilen. electron-builder ist dafür das Standardwerkzeug:

{
  "build": {
    "appId": "com.example.myapp",
    "productName": "My Electron App",
    "directories": {
      "output": "dist"
    },
    "win": {
      "target": ["nsis", "portable"],
      "icon": "assets/icon.ico"
    },
    "mac": {
      "target": ["dmg", "zip"],
      "icon": "assets/icon.icns",
      "category": "public.app-category.developer-tools"
    },
    "linux": {
      "target": ["AppImage", "deb"],
      "icon": "assets/icon.png",
      "category": "Development"
    },
    "publish": [
      {
        "provider": "github",
        "owner": "dein-username",
        "repo": "dein-repo"
      }
    ]
  }
}
# Bauen für die aktuelle Plattform
npm run build

# Für alle Plattformen (erfordert ggf. spezielle Umgebung)
npx electron-builder --win --mac --linux

Auto-Updates 🔄

Eine der coolsten Features von Electron ist die Möglichkeit, automatische Updates auszuliefern. Mit electron-updater (Teil von electron-builder) geht das so:

// main.js - Auto-Update Setup
const { autoUpdater } = require('electron-updater');

app.whenReady().then(() => {
  createWindow();

  // Updates prüfen
  autoUpdater.checkForUpdatesAndNotify();
});

autoUpdater.on('update-available', (info) => {
  // Renderer benachrichtigen
  mainWindow.webContents.send('update-available', info);
});

autoUpdater.on('update-downloaded', (info) => {
  // Update installieren und App neustarten
  autoUpdater.quitAndInstall();
});

Damit das funktioniert, musst du deine Releases auf GitHub (oder einem anderen Provider) hosten. electron-builder kümmert sich um den Rest.

Sicherheit, Das musst du wissen 🔒

Sicherheit ist bei Electron kritisch, weil deine App vollen Systemzugriff hat. Hier die wichtigsten Regeln:

EinstellungEmpfehlungWarum?
nodeIntegrationfalseVerhindert direkten Node.js-Zugriff im Renderer
contextIsolationtrueIsoliert Preload-Script vom Webinhalt
sandboxtrueZusätzliche Prozess-Isolation
webSecuritytrueAktiviert Same-Origin-Policy

Zusätzliche Tipps:

  • Validiere IPC-Inputs, Vertraue niemals Daten aus dem Renderer
  • Lade keine Remote-Inhalte ohne Content Security Policy
  • Halte Electron aktuell, Chromium-Updates patchen Sicherheitslücken
  • Nutze keine shell.openExternal() mit unvalidierten URLs

Performance-Tipps ⚡

Electron-Apps haben den Ruf, Ressourcen-hungrig zu sein. Mit diesen Tipps holäst du das Maximum raus:

  • Lazy Loading, Lade Module erst wenn sie gebraucht werden
  • BrowserWindow sinnvoll nutzen, Nicht benötigte Fenster schließen oder win.hide() verwenden
  • V8 Snapshots, Startup-Zeit mit v8-compile-cache verkürzen
  • DevTools in Production deaktivieren
  • Background Throttling, backgroundThrottling: true für nicht-sichtbare Fenster
  • Asar-Packaging, App-Dateien in ein asar-Archiv packen für schnelleres Laden
// Lazy Loading Beispiel
async function loadHeavyModule() {
  const { heavyFunction } = await import('./heavy-module.js');
  return heavyFunction();
}

// Statt sofort beim Start alles zu laden
app.whenReady().then(async () => {
  createWindow();
  // Schwere Module erst nach dem Fenster laden
  setTimeout(async () => {
    const analytics = await import('./analytics.js');
    analytics.init();
  }, 2000);
});

Node.js-Backend-Integration 🖥️

Da Electron Node.js eingebaut hat, kannst du direkt Backend-Logik in deiner App nutzen. Wenn du allerdings einen richtigen Backend-Server brauchst, zum Beispiel für eine API oder Datenbankanbindung, kann NestJS eine super Ergänzung sein:

NestJS: Server-Framework auf Steroide 🎉
NestJS bringt Struktur und Architektur in deine Node.js-Backend-Entwicklung.

Wiederverwendbare UI-Komponenten 🧱

Ein großer Vorteil von Electron: Du kannst deine UI-Komponenten zwischen Web-App und Desktop-App teilen. Web Components eignen sich hier besonders gut, weil sie framework-agnostisch sind:

Web Components: Eigene HTML-Elemente ohne Framework 🧱
Web Components sind native Browser-APIs für eigene HTML-Elemente – Shadow DOM, Templates und Custom Elements ganz ohne Framework.

Electron vs. Tauri, Der Vergleich 🥊

Tauri ist der aufstrebende Herausforderer. Hier ein ehrlicher Vergleich:

KriteriumElectronTauri
Sprache (Backend)JavaScript/Node.jsRust
Rendering EngineChromium (gebundelt)System WebView
Bundle-Größe~150 MB+~5-10 MB
RAM-VerbrauchHöherNiedriger
ÖkosystemRiesig, ausgereiftWachsend
Lernkurve (Web-Devs)NiedrigMittel (Rust)
PlattformenWin/Mac/LinuxWin/Mac/Linux/Mobile
StabilitätBattle-testedGut, aber jünger

Meine Einschätzung: Wenn du aus der Web-Welt kommst und schnell loslegen willst, ist Electron die sichere Wahl. Wenn Bundle-Größe und Performance kritisch sind und du bereit bist, Rust zu lernen, ist Tauri extrem spannend.

Real-World Beispiele 🌎

Diese Apps nutzen Electron und du nutzt sie vermutlich täglich:

  • VS Code, Der populärste Code-Editor überhaupt
  • Slack, Team-Kommunikation
  • Discord, Gaming & Community Chat
  • Obsidian, Notizen und Wissensmanagement
  • Figma Desktop, Design-Tool
  • Notion Desktop, Produktivität
  • 1Password, Passwort-Manager

Das zeigt: Electron ist absolut production-ready für Apps jeder Größe.

Fazit 🎯

Electron ist nach wie vor das mächtigste Framework für Cross-Platform Desktop-Apps, wenn du aus der Web-Welt kommst. Ja, die Bundle-Größe ist größer als bei nativen Apps, und der RAM-Verbrauch ist höher. Aber dafür bekommst du:

  • Das gesamte Web-Ökosystem (npm, Frameworks, Tools)
  • Eine riesige Community und Dokumentation
  • Battle-tested in Millionen von Installationen
  • Schnelle Entwicklung mit bekannten Technologien

Probier es aus, mit den Sicherheits- und Performance-Tipps aus diesem Artikel bist du bestens aufgestellt! 🚀