Skip to content

Kapitel 9: Netzwerkanfragen und lokaler Speicher

In diesem Kapitel lernen Sie, wie Sie Netzwerkanfragen in Electron durchführen und Daten lokal speichern.

9.1 Netzwerkanfragen im Renderer-Prozess (axios/fetch)

fetch-API verwenden (Standard, kein zusätzliches Paket)

javascript
// renderer.js oder Preload
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP-Fehler: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('Daten abgerufen:', data);
    return data;
  } catch (error) {
    console.error('Fehler beim Abrufen:', error);
    throw error;
  }
}

axios verwenden (beliebte Bibliothek)

bash
# axios installieren
npm install axios
javascript
// preload.js oder renderer.js
const axios = require('axios');  // CommonJS
// oder: import axios from 'axios';  // ES Module

async function fetchData() {
  try {
    const response = await axios.get('https://api.example.com/data', {
      headers: {
        'Content-Type': 'application/json'
      }
    });
    
    console.log('Daten abgerufen:', response.data);
    return response.data;
  } catch (error) {
    console.error('Fehler:', error.message);
    throw error;
  }
}

// POST-Anfrage
async function postData(data) {
  try {
    const response = await axios.post('https://api.example.com/data', data, {
      headers: {
        'Authorization': 'Bearer token123'
      }
    });
    return response.data;
  } catch (error) {
    console.error('Fehler:', error);
    throw error;
  }
}

Praxisbeispiel: API-Daten abrufen und anzeigen

html
<!-- index.html -->
<div>
  <button onclick="loadUsers()">Benutzer laden</button>
  <div id="userList"></div>
</div>

<script>
  async function loadUsers() {
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const users = await response.json();
      
      const userList = document.getElementById('userList');
      userList.innerHTML = '<ul>' + 
        users.map(user => `<li>${user.name} (${user.email})</li>`).join('') +
        '</ul>';
    } catch (error) {
      console.error('Fehler:', error);
      alert('Fehler beim Laden der Benutzer');
    }
  }
</script>

9.2 Netzwerkanfragen im Hauptprozess

request-Modul verwenden (für komplexe Anfragen)

bash
# request ist veraltet, aber in Electron noch nützlich
# Alternative: axios oder node-fetch
npm install axios
javascript
// main.js (Hauptprozess)
const axios = require('axios');
const { ipcMain } = require('electron');

// API-Anfrage im Hauptprozess
async function fetchFromAPI(endpoint, params) {
  try {
    const response = await axios.get(`https://api.example.com/${endpoint}`, {
      params,
      timeout: 10000,  // 10 Sekunden Timeout
      headers: {
        'User-Agent': 'MyElectronApp/1.0'
      }
    });
    return { success: true, data: response.data };
  } catch (error) {
    console.error('API-Fehler:', error.message);
    return { success: false, error: error.message };
  }
}

// IPC-Handler
ipcMain.handle('fetch-api-data', async (event, { endpoint, params }) => {
  return await fetchFromAPI(endpoint, params);
});

// POST-Anfrage
ipcMain.handle('post-api-data', async (event, { endpoint, data }) => {
  try {
    const response = await axios.post(`https://api.example.com/${endpoint}`, data, {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
      }
    });
    return { success: true, data: response.data };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

Netzwerkanfragen über IPC (Renderer → Main → API)

javascript
// preload.js
contextBridge.exposeInMainWorld('api', {
  fetchData: (endpoint, params) => 
    ipcRenderer.invoke('fetch-api-data', { endpoint, params }),
  postData: (endpoint, data) => 
    ipcRenderer.invoke('post-api-data', { endpoint, data })
});
html
<!-- index.html -->
<script>
  async function loadData() {
    try {
      const result = await window.api.fetchData('users', { limit: 10 });
      if (result.success) {
        console.log('Daten:', result.data);
      } else {
        alert('Fehler: ' + result.error);
      }
    } catch (error) {
      console.error('IPC-Fehler:', error);
    }
  }
</script>

9.3 CORS-Probleme lösen

CORS in Electron verstehen

In Electron gibt es zwei Szenarien:

  1. Renderer-Prozess: Wie im Browser (CORS-Beschränkungen)
  2. Hauptprozess: Keine CORS-Beschränkungen (Node.js)

Lösung 1: Anfragen über Hauptprozess (empfohlen)

javascript
// main.js
const { ipcMain } = require('electron');
const axios = require('axios');

ipcMain.handle('fetch-cors-data', async (event, url) => {
  try {
    // Kein CORS im Hauptprozess!
    const response = await axios.get(url);
    return { success: true, data: response.data };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

Lösung 2: webSecurity deaktivieren (nicht empfohlen, nur Entwicklung)

javascript
// main.js (Nicht für Produktion!)
const win = new BrowserWindow({
  webPreferences: {
    webSecurity: false,  // CORS deaktivieren (UNSICHER!)
    nodeIntegration: true,
    contextIsolation: false
  }
});

Lösung 3: CORS-Proxy im Hauptprozess erstellen

javascript
// main.js
const express = require('express');
const axios = require('axios');
const cors = require('cors');

function createProxyServer() {
  const app = express();
  app.use(cors());
  app.use(express.json());

  // Proxy-Endpunkt
  app.all('/proxy', async (req, res) => {
    try {
      const targetUrl = req.body.url;
      const method = req.body.method || 'GET';
      const data = req.body.data;

      const response = await axios({
        method,
        url: targetUrl,
        data,
        headers: req.body.headers || {}
      });

      res.json({ success: true, data: response.data });
    } catch (error) {
      res.status(500).json({ success: false, error: error.message });
    }
  });

  app.listen(3001, () => {
    console.log('Proxy-Server läuft auf Port 3001');
  });
}

app.whenReady().then(() => {
  createWindow();
  createProxyServer();  // Proxy starten
});
javascript
// renderer.js - Proxy verwenden
async function fetchWithProxy(url) {
  const response = await fetch('http://localhost:3001/proxy', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ url, method: 'GET' })
  });
  return response.json();
}

9.4 Lokale Speicherlösungen

Lösung 1: localStorage (Renderer-Prozess, leichtgewichtig)

javascript
// renderer.js
// Daten speichern
function saveToLocalStorage(key, value) {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    console.log('Gespeichert:', key);
  } catch (error) {
    console.error('Fehler beim Speichern:', error);
  }
}

// Daten abrufen
function getFromLocalStorage(key) {
  try {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : null;
  } catch (error) {
    console.error('Fehler beim Abrufen:', error);
    return null;
  }
}

// Daten löschen
function removeFromLocalStorage(key) {
  localStorage.removeItem(key);
}

// Alle Daten löschen
function clearLocalStorage() {
  localStorage.clear();
}

// Beispiel
saveToLocalStorage('user', { name: 'Max', age: 25 });
const user = getFromLocalStorage('user');
console.log(user.name);  // "Max"

Lösung 2: electron-store (Hauptprozess, Persistenz, empfohlen)

bash
# electron-store installieren
npm install electron-store
javascript
// main.js (Hauptprozess)
const Store = require('electron-store').default;
// oder: import Store from 'electron-store';  // ES Module

// Store initialisieren
const store = new Store();

// Daten speichern
store.set('user.name', 'Max');
store.set('user.age', 25);
store.set('settings.theme', 'dark');

// Daten abrufen
const userName = store.get('user.name');  // "Max"
const theme = store.get('settings.theme', 'light');  // Standard: 'light'
const allSettings = store.get('settings');  // Ganzes Objekt

// Daten löschen
store.delete('user.age');
store.delete('settings');  // Ganzes Objekt löschen

// Alle Daten abrufen
const allData = store.store;

// Store zurücksetzen
store.clear();

// IPC-Handler für Renderer
const { ipcMain } = require('electron');

ipcMain.handle('store-get', (event, key) => {
  return store.get(key);
});

ipcMain.handle('store-set', (event, key, value) => {
  store.set(key, value);
  return { success: true };
});

ipcMain.handle('store-delete', (event, key) => {
  store.delete(key);
  return { success: true };
});
javascript
// preload.js
contextBridge.exposeInMainWorld('storeAPI', {
  get: (key) => ipcRenderer.invoke('store-get', key),
  set: (key, value) => ipcRenderer.invoke('store-set', key, value),
  delete: (key) => ipcRenderer.invoke('store-delete', key)
});
html
<!-- index.html -->
<script>
  async function saveSetting() {
    await window.storeAPI.set('theme', 'dark');
    console.log('Theme gespeichert');
  }

  async function loadSetting() {
    const theme = await window.storeAPI.get('theme');
    console.log('Theme:', theme);
  }
</script>

Lösung 3: Datenbankspeicherung (SQLite, MySQL mit Node.js)

SQLite verwenden

bash
# sqlite3 installieren
npm install sqlite3
javascript
// main.js (Hauptprozess)
const sqlite3 = require('sqlite3').verbose();
const path = require('path');

// Datenbank initialisieren
const db = new sqlite3.Database(
  path.join(app.getPath('userData'), 'database.db'),
  (err) => {
    if (err) {
      console.error('Datenbankfehler:', err.message);
    } else {
      console.log('Mit SQLite-Datenbank verbunden');
      createTables();
    }
  }
);

// Tabellen erstellen
function createTables() {
  db.run(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `);
}

// Daten einfügen
function insertUser(name, email) {
  return new Promise((resolve, reject) => {
    db.run(
      'INSERT INTO users (name, email) VALUES (?, ?)',
      [name, email],
      function(err) {
        if (err) reject(err);
        else resolve({ id: this.lastID });
      }
    );
  });
}

// Daten abfragen
function getUsers() {
  return new Promise((resolve, reject) => {
    db.all('SELECT * FROM users', [], (err, rows) => {
      if (err) reject(err);
      else resolve(rows);
    });
  });
}

// IPC-Handler
ipcMain.handle('db-insert-user', async (event, { name, email }) => {
  try {
    const result = await insertUser(name, email);
    return { success: true, id: result.id };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

ipcMain.handle('db-get-users', async (event) => {
  try {
    const users = await getUsers();
    return { success: true, users };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

MySQL verwenden

bash
# mysql2 installieren
npm install mysql2
javascript
// main.js
const mysql = require('mysql2/promise');

// Datenbankverbindung
async function createConnection() {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'electron_app'
  });
  return connection;
}

// IPC-Handler
ipcMain.handle('mysql-query', async (event, { sql, params }) => {
  try {
    const connection = await createConnection();
    const [rows] = await connection.execute(sql, params);
    await connection.end();
    return { success: true, data: rows };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

Vergleich der Speicherlösungen

LösungProzessPersistenzKomplexitätEmpfohlen für
localStorageRendererJa (lokal)NiedrigKleine Daten, temporär
electron-storeMainJa (JSON-Datei)NiedrigEinstellungen, Konfiguration
SQLiteMainJa (Datenbankdatei)MittelStrukturierte Daten, große Mengen
MySQLMainJa (Server)HochEnterprise, Multi-User

9.5 Praxisbeispiel: Netzwerkanfrage senden, lokale Datenspeicherung

Projekt: Wetter-App mit localStorage und electron-store

main.js (Hauptprozess)

javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const Store = require('electron-store').default;
const axios = require('axios');

const store = new Store();

function createWindow() {
  const win = new BrowserWindow({
    width: 1000,
    height: 700,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  });

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

// Wetterdaten abrufen (kein CORS-Problem im Hauptprozess)
ipcMain.handle('fetch-weather', async (event, city) => {
  try {
    const apiKey = 'YOUR_API_KEY';  // OpenWeatherMap API
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=de`;
    
    const response = await axios.get(url);
    return { success: true, data: response.data };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

// Einstellungen speichern/abrufen
ipcMain.handle('get-setting', (event, key) => {
  return store.get(key);
});

ipcMain.handle('set-setting', (event, key, value) => {
  store.set(key, value);
  return { success: true };
});

app.whenReady().then(createWindow);

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

preload.js (IPC-Brücke)

javascript
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('weatherAPI', {
  fetchWeather: (city) => ipcRenderer.invoke('fetch-weather', city),
  getSetting: (key) => ipcRenderer.invoke('get-setting', key),
  setSetting: (key, value) => ipcRenderer.invoke('set-setting', key, value)
});

index.html (UI)

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Wetter-App</title>
  <style>
    body { font-family: Arial; padding: 20px; max-width: 800px; margin: 0 auto; }
    .search-box { margin: 20px 0; }
    input { padding: 8px; width: 300px; }
    button { padding: 8px 16px; margin-left: 10px; }
    .weather-card { 
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 20px;
      border-radius: 10px;
      margin-top: 20px;
      display: none;
    }
    .history { margin-top: 20px; }
    .history-item { 
      padding: 5px; 
      cursor: pointer; 
      color: blue; 
      text-decoration: underline; 
    }
  </style>
</head>
<body>
  <h1>Wetter-App</h1>
  
  <div class="search-box">
    <input type="text" id="cityInput" placeholder="Stadt eingeben (z.B. Berlin)">
    <button onclick="searchWeather()">Suchen</button>
  </div>

  <div id="weatherCard" class="weather-card">
    <h2 id="cityName"></h2>
    <p id="temperature"></p>
    <p id="description"></p>
    <p id="humidity"></p>
  </div>

  <div class="history">
    <h3>Suchverlauf (localStorage)</h3>
    <div id="searchHistory"></div>
  </div>

  <script>
    // Suchverlauf aus localStorage laden
    function loadSearchHistory() {
      const history = JSON.parse(localStorage.getItem('weatherHistory') || '[]');
      const container = document.getElementById('searchHistory');
      container.innerHTML = '';
      
      history.forEach(city => {
        const div = document.createElement('div');
        div.className = 'history-item';
        div.textContent = city;
        div.onclick = () => {
          document.getElementById('cityInput').value = city;
          searchWeather();
        };
        container.appendChild(div);
      });
    }

    // Stadt zum Verlauf hinzufügen
    function addToHistory(city) {
      let history = JSON.parse(localStorage.getItem('weatherHistory') || '[]');
      if (!history.includes(city)) {
        history.unshift(city);
        if (history.length > 5) history = history.slice(0, 5);  // Nur 5 Einträge
        localStorage.setItem('weatherHistory', JSON.stringify(history));
        loadSearchHistory();
      }
    }

    // Wetter suchen
    async function searchWeather() {
      const city = document.getElementById('cityInput').value.trim();
      if (!city) return;

      try {
        const result = await window.weatherAPI.fetchWeather(city);
        
        if (result.success) {
          const data = result.data;
          
          // UI aktualisieren
          document.getElementById('cityName').textContent = data.name;
          document.getElementById('temperature').textContent = `Temperatur: ${Math.round(data.main.temp)}°C`;
          document.getElementById('description').textContent = `Beschreibung: ${data.weather[0].description}`;
          document.getElementById('humidity').textContent = `Luftfeuchtigkeit: ${data.main.humidity}%`;
          
          document.getElementById('weatherCard').style.display = 'block';
          
          // Zum Verlauf hinzufügen
          addToHistory(city);
          
          // Einstellung speichern (letzte Stadt)
          await window.weatherAPI.setSetting('lastCity', city);
        } else {
          alert('Fehler: ' + result.error);
        }
      } catch (error) {
        console.error('Fehler:', error);
        alert('Fehler beim Abrufen der Wetterdaten');
      }
    }

    // Beim Laden: letzte Stadt laden
    async function loadLastCity() {
      const lastCity = await window.weatherAPI.getSetting('lastCity');
      if (lastCity) {
        document.getElementById('cityInput').value = lastCity;
      }
      loadSearchHistory();
    }

    loadLastCity();
  </script>
</body>
</html>

Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • Netzwerkanfragen mit fetch und axios im Renderer-Prozess durchzuführen
  • Netzwerkanfragen im Hauptprozess auszuführen (kein CORS-Problem)
  • CORS-Probleme in Electron zu lösen
  • Lokale Speicherlösungen (localStorage, electron-store, Datenbanken) zu verwenden
  • Ein praktisches Beispiel mit Netzwerkanfrage und Datenspeicherung zu implementieren

Im nächsten Kapitel werden wir debugging und Fehlerbehandlung behandeln.

Frei für alle Anfänger