fetch & HTTP Grundlagen
Bisher lebten alle Daten im Arbeitsspeicher oder im localStorage. In echten Anwendungen kommen Daten von einem Server — per HTTP. Bevor wir React damit verbinden, schauen wir uns an, wie das in JavaScript und TypeScript funktioniert.
Als Datenquelle verwenden wir JSONPlaceholder — eine öffentliche Mock-API, die echte HTTP-Anfragen beantwortet, ohne dass wir ein Backend aufsetzen müssen.
Das Todo-Format
Abschnitt betitelt „Das Todo-Format“JSONPlaceholder liefert Todos in diesem Format:
type Todo = { id: number title: string completed: boolean userId: number}Die Endpunkte, die wir verwenden:
| Methode | URL | Beschreibung |
|---|---|---|
| GET | /todos | Alle Todos (200 Einträge) |
| GET | /todos/:id | Ein einzelnes Todo |
| POST | /todos | Neues Todo anlegen (Mock) |
| PATCH | /todos/:id | Todo aktualisieren (Mock) |
| DELETE | /todos/:id | Todo löschen (Mock) |
fetch: ein GET-Request
Abschnitt betitelt „fetch: ein GET-Request“fetch ist die native Browser-API für HTTP-Anfragen. Sie gibt ein Promise zurück:
const response = await fetch('https://jsonplaceholder.typicode.com/todos')const todos = await response.json()console.log(todos) // Array mit 200 Todosfetch löst das Promise sobald der Server antwortet — unabhängig davon, ob die Antwort ein Fehler ist. Ein 404 oder 500 ist kein abgelehntes Promise. Deshalb muss response.ok explizit geprüft werden:
const response = await fetch('https://jsonplaceholder.typicode.com/todos')
if (!response.ok) { throw new Error(`HTTP-Fehler: ${response.status}`)}
const todos = await response.json()TypeScript: Rückgabetyp angeben
Abschnitt betitelt „TypeScript: Rückgabetyp angeben“response.json() gibt Promise<any> zurück — TypeScript weiß nichts über die Struktur. Du kannst den Typ per Cast angeben:
type Todo = { id: number title: string completed: boolean userId: number}
async function fetchTodos(): Promise<Todo[]> { const response = await fetch('https://jsonplaceholder.typicode.com/todos') if (!response.ok) throw new Error(`HTTP-Fehler: ${response.status}`) return response.json() as Promise<Todo[]>}
async function fetchTodo(id: number): Promise<Todo> { const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`) if (!response.ok) throw new Error(`HTTP-Fehler: ${response.status}`) return response.json() as Promise<Todo>}TypeScript vertraut dir beim Cast — es prüft nicht, ob die API wirklich dieses Format zurückgibt. Für produktionskritische Daten lohnt sich eine Validierungsbibliothek wie Zod.
POST: Daten senden
Abschnitt betitelt „POST: Daten senden“Für schreibende Anfragen übergibt man Methode, Header und Body:
async function createTodo(title: string): Promise<Todo> { const response = await fetch('https://jsonplaceholder.typicode.com/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, completed: false, userId: 1 }), }) if (!response.ok) throw new Error(`HTTP-Fehler: ${response.status}`) return response.json() as Promise<Todo>}JSONPlaceholder gibt das gesendete Objekt mit einer generierten id zurück — der Datensatz wird aber nicht wirklich gespeichert.
axios als Alternative
Abschnitt betitelt „axios als Alternative“axios ist eine weit verbreitete HTTP-Bibliothek mit einem ergonomischeren API:
pnpm add axiosDer wichtigste Unterschied zu fetch:
- Automatisches JSON-Parsing — kein manuelles
.json()nötig - Fehler bei HTTP-Fehlerstatus — ein 404 oder 500 wirft direkt einen Fehler,
response.okentfällt - Interceptors — zentrales Logging, Auth-Token injizieren, globale Fehlerbehandlung
import axios from 'axios'
const client = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com',})
async function fetchTodos(): Promise<Todo[]> { const { data } = await client.get<Todo[]>('/todos') return data}
async function fetchTodo(id: number): Promise<Todo> { const { data } = await client.get<Todo>(`/todos/${id}`) return data}
async function createTodo(title: string): Promise<Todo> { const { data } = await client.post<Todo>('/todos', { title, completed: false, userId: 1, }) return data}client.get<Todo[]>('/todos') gibt direkt { data: Todo[] } zurück — kein Cast auf response.json(), der Typ kommt vom Generic.
fetch vs. axios
Abschnitt betitelt „fetch vs. axios“fetch | axios | |
|---|---|---|
| Eingebaut im Browser | ja | nein (npm-Paket) |
| JSON-Parsing | manuell | automatisch |
| HTTP-Fehler als Exception | nein | ja |
| Interceptors | nein | ja |
| Paketgröße | 0 kb | ~14 kb |
Für einfache Requests reicht fetch. Sobald du Auth-Token, globale Fehlerbehandlung oder Interceptors brauchst, spart axios viel Boilerplate.
Das Problem: Daten in React anzeigen
Abschnitt betitelt „Das Problem: Daten in React anzeigen“Diese Funktionen laufen gut in Node oder der Browser-Konsole. Aber wie binden wir sie in eine React-Komponente ein? Wir können fetchTodos() nicht direkt im Komponentenrumpf aufrufen — das würde bei jedem Render eine neue Anfrage starten und eine Endlosschleife auslösen.
React braucht einen Mechanismus, um Seiteneffekte — also alles, was über das reine Rendering hinausgeht — kontrolliert auszuführen. Das ist useEffect, das nächste Kapitel.