Fetch API & REST verstehen
Fetch API & REST verstehen
Die Fetch API ist der moderne Standard für HTTP-Requests in JavaScript. In diesem Tutorial lernst du, wie du mit Servern und APIs kommunizierst.
Was ist die Fetch API?
fetch() ist die native Browser-Funktion für Netzwerk-Anfragen:
// Einfachster fetch
fetch("https://api.example.com/users")
.then(response => response.json())
.then(data => console.log(data));
Mit async/await (empfohlen)
async function getUsers() {
const response = await fetch("https://api.example.com/users");
const data = await response.json();
return data;
}
REST API Grundlagen
REST (Representational State Transfer) ist ein Standard für API-Design:
| HTTP-Methode | Aktion | Beispiel |
|---|---|---|
| GET | Daten lesen | Alle User holen |
| POST | Daten erstellen | Neuen User anlegen |
| PUT | Daten komplett ersetzen | User aktualisieren |
| PATCH | Daten teilweise ändern | Nur Name ändern |
| DELETE | Daten löschen | User löschen |
Typische REST-Endpunkte
GET /api/users → Alle User
GET /api/users/1 → User mit ID 1
POST /api/users → Neuen User erstellen
PUT /api/users/1 → User 1 komplett ersetzen
PATCH /api/users/1 → User 1 teilweise ändern
DELETE /api/users/1 → User 1 löschen
GET - Daten laden
Einfacher GET-Request
async function getUsers() {
const response = await fetch("https://api.example.com/users");
const users = await response.json();
return users;
}
// Verwenden
const users = await getUsers();
console.log(users);
Mit Query-Parametern
// URL mit Parametern bauen
const params = new URLSearchParams({
page: 1,
limit: 10,
sort: "name"
});
const response = await fetch(`https://api.example.com/users?${params}`);
// URL: https://api.example.com/users?page=1&limit=10&sort=name
Response prüfen
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
// Status prüfen!
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
POST - Daten senden
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
// Verwenden
const newUser = await createUser({
name: "Max Mustermann",
email: "max@example.com"
});
console.log("Erstellt:", newUser);
PUT/PATCH - Daten aktualisieren
PUT - Komplett ersetzen
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
// Alle Felder müssen angegeben werden!
await updateUser(1, {
name: "Max",
email: "max@example.com",
role: "admin"
});
PATCH - Teilweise ändern
async function patchUser(id, partialData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(partialData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
// Nur das ändern was nötig ist
await patchUser(1, { name: "Maximilian" });
DELETE - Daten löschen
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: "DELETE"
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// DELETE gibt oft keinen Body zurück
return response.ok;
}
// Verwenden
if (await deleteUser(1)) {
console.log("User gelöscht");
}
Das Response-Objekt
const response = await fetch(url);
// Status
console.log(response.status); // 200, 404, 500, ...
console.log(response.statusText); // "OK", "Not Found", ...
console.log(response.ok); // true wenn 200-299
// Headers
console.log(response.headers.get("Content-Type"));
console.log(response.headers.get("X-Total-Count"));
// Body lesen (nur einmal möglich!)
const json = await response.json(); // Als JSON
const text = await response.text(); // Als Text
const blob = await response.blob(); // Als Blob (Dateien)
const buffer = await response.arrayBuffer(); // Als ArrayBuffer
Headers setzen
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer eyJhbGci...",
"X-Custom-Header": "Wert",
"Accept": "application/json"
},
body: JSON.stringify(data)
});
Mit Headers-Objekt
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("Authorization", `Bearer ${token}`);
const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(data)
});
Fehlerbehandlung
HTTP-Fehler prüfen
async function fetchWithError(url) {
const response = await fetch(url);
if (!response.ok) {
// Versuche Fehler-Details zu lesen
let errorMessage;
try {
const errorData = await response.json();
errorMessage = errorData.message || response.statusText;
} catch {
errorMessage = response.statusText;
}
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
}
return response.json();
}
Netzwerk-Fehler
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "TypeError") {
// Netzwerk-Fehler (offline, CORS, etc.)
console.error("Netzwerk-Fehler:", error.message);
} else {
// Anderer Fehler
console.error("Fehler:", error.message);
}
throw error;
}
}
Timeout implementieren
fetch hat keinen eingebauten Timeout, aber wir können einen bauen:
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
throw new Error("Request timeout");
}
throw error;
}
}
// Verwendung
try {
const response = await fetchWithTimeout("/api/data", {}, 3000);
const data = await response.json();
} catch (error) {
console.log(error.message); // "Request timeout"
}
Request abbrechen
const controller = new AbortController();
// Request starten
const fetchPromise = fetch("/api/data", {
signal: controller.signal
});
// Später abbrechen
controller.abort();
// Im UI
const controller = new AbortController();
const abortBtn = document.querySelector("#abort");
abortBtn.addEventListener("click", () => {
controller.abort();
});
try {
const response = await fetch(url, { signal: controller.signal });
} catch (error) {
if (error.name === "AbortError") {
console.log("Request abgebrochen");
}
}
API-Client erstellen
Einfacher Client
const API_BASE = "https://api.example.com";
const api = {
async get(endpoint) {
const response = await fetch(`${API_BASE}${endpoint}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
async post(endpoint, data) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
async put(endpoint, data) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
async delete(endpoint) {
const response = await fetch(`${API_BASE}${endpoint}`, {
method: "DELETE"
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.ok;
}
};
// Verwendung
const users = await api.get("/users");
const newUser = await api.post("/users", { name: "Max" });
await api.put("/users/1", { name: "Max", email: "max@test.de" });
await api.delete("/users/1");
Erweiterter Client mit Auth
function createApiClient(baseUrl, getToken) {
async function request(endpoint, options = {}) {
const url = `${baseUrl}${endpoint}`;
const config = {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
}
};
// Token hinzufügen wenn vorhanden
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
const response = await fetch(url, config);
if (!response.ok) {
const error = new Error(`HTTP ${response.status}`);
error.status = response.status;
try {
error.data = await response.json();
} catch {}
throw error;
}
// Leere Response behandeln
const text = await response.text();
return text ? JSON.parse(text) : null;
}
return {
get: (endpoint) => request(endpoint),
post: (endpoint, data) => request(endpoint, {
method: "POST",
body: JSON.stringify(data)
}),
put: (endpoint, data) => request(endpoint, {
method: "PUT",
body: JSON.stringify(data)
}),
patch: (endpoint, data) => request(endpoint, {
method: "PATCH",
body: JSON.stringify(data)
}),
delete: (endpoint) => request(endpoint, { method: "DELETE" })
};
}
// Verwendung
const api = createApiClient(
"https://api.example.com",
() => localStorage.getItem("token")
);
const users = await api.get("/users");
FormData senden
Für Datei-Uploads und Formulare:
const form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Datei hinzufügen
const fileInput = document.querySelector('input[type="file"]');
formData.append("avatar", fileInput.files[0]);
// KEIN Content-Type Header setzen! Browser macht das automatisch
const response = await fetch("/api/upload", {
method: "POST",
body: formData
});
const result = await response.json();
console.log("Upload erfolgreich:", result);
});
Parallele Requests
// Alle gleichzeitig
const [users, posts, comments] = await Promise.all([
api.get("/users"),
api.get("/posts"),
api.get("/comments")
]);
// Mit Fehlertoleranz
const results = await Promise.allSettled([
api.get("/users"),
api.get("/maybe-fails"),
api.get("/posts")
]);
const successfulData = results
.filter(r => r.status === "fulfilled")
.map(r => r.value);
Praktisches Beispiel: User-Management
const userService = {
baseUrl: "/api/users",
async getAll(filters = {}) {
const params = new URLSearchParams(filters);
const response = await fetch(`${this.baseUrl}?${params}`);
if (!response.ok) throw new Error("Laden fehlgeschlagen");
return response.json();
},
async getById(id) {
const response = await fetch(`${this.baseUrl}/${id}`);
if (response.status === 404) return null;
if (!response.ok) throw new Error("Laden fehlgeschlagen");
return response.json();
},
async create(userData) {
const response = await fetch(this.baseUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Erstellen fehlgeschlagen");
}
return response.json();
},
async update(id, userData) {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error("Update fehlgeschlagen");
return response.json();
},
async delete(id) {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: "DELETE"
});
if (!response.ok) throw new Error("Löschen fehlgeschlagen");
return true;
}
};
// Verwendung
const users = await userService.getAll({ role: "admin" });
const user = await userService.getById(1);
const newUser = await userService.create({ name: "Max", email: "max@test.de" });
await userService.update(1, { name: "Maximilian" });
await userService.delete(1);
Zusammenfassung
| Methode | Verwendung |
|---|---|
GET | Daten lesen |
POST | Daten erstellen |
PUT | Komplett ersetzen |
PATCH | Teilweise ändern |
DELETE | Löschen |
// Basis-Pattern
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
Übung: Baue einen Blog-API-Client mit Methoden für Posts und Kommentare. Nutze die JSONPlaceholder API: https://jsonplaceholder.typicode.com