Appearance
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 axiosjavascript
// 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 axiosjavascript
// 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:
- Renderer-Prozess: Wie im Browser (CORS-Beschränkungen)
- 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-storejavascript
// 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 sqlite3javascript
// 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 mysql2javascript
// 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ösung | Prozess | Persistenz | Komplexität | Empfohlen für |
|---|---|---|---|---|
| localStorage | Renderer | Ja (lokal) | Niedrig | Kleine Daten, temporär |
| electron-store | Main | Ja (JSON-Datei) | Niedrig | Einstellungen, Konfiguration |
| SQLite | Main | Ja (Datenbankdatei) | Mittel | Strukturierte Daten, große Mengen |
| MySQL | Main | Ja (Server) | Hoch | Enterprise, 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
fetchundaxiosim 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.
