Von Katharina „Kat“ Schmidt & Nova Trent, Frontend-Team bei Java Fleet Systems Consulting
Schwierigkeit: 🟡 Mittel
Lesezeit: 45 Minuten
Voraussetzungen: Vue.js Basics (Teil 1-4), Routing Basics (Teil 8) | Kein Vorwissen? → Starte mit Teil 1
📚 Was bisher geschah – Vue.js für Anfänger
Bereits veröffentlicht:
- ✅ Teil 1: Setup & Erste API-Anbindung – Nova’s erstes Vue-Projekt
- ✅ Teil 2: Styling & Responsive Design – Tailwind CSS & Mobile-First
- ✅ Teil 3: Formulare & User Input – v-model & Two-Way Binding
- ✅ Teil 4: Listen-Manipulation & Reaktivität – ref(), computed(), watchers
- ✅ Teil 5: CRUD Complete – Edit, Delete & Optimistic UI
- ✅ Teil 6: Component Communication – Props, Emit & Custom Events
- ✅ Teil 7: State Management – Composables & Pinia Basics
- ✅ Teil 8: Routing & Multi-Page App – Vue Router Setup
Heute: Teil 9 – API Integration & Error Handling 🛡️
Neu in der Serie? Kein Problem!
- 🌱 Einsteiger? Starte mit Teil 1 für den vollen Kontext
- 🌿 Erfahren? Du kannst hier einsteigen – wir erklären Basics kurz
⚡ Das Wichtigste in 30 Sekunden
Dein Problem: Deine App zeigt alert("Fehler!") bei jedem Problem. Keine Loading-States, User warten ins Leere, und wenn der Server nicht antwortet… Chaos!
Die Lösung: Professionelles Error Handling mit Axios, Interceptors, Loading-Skeletons und einem zentralen Toast-System!
Heute lernst du:
- ✅ Warum Axios statt fetch() (und wann fetch() reicht)
- ✅ Interceptors für Auth & Error-Handling
- ✅ Die 3 States: Loading → Error → Success
- ✅ Toast Notifications bauen
- ✅ Retry-Logic für bessere UX
Für wen ist dieser Artikel?
- 🌱 Anfänger: Du lernst Error Handling von Grund auf
- 🌿 Erfahrene: Du vertiefst Best Practices für Production
- 🌳 Profis: Im Bonus findest du Advanced Interceptor Patterns
Zeit-Investment: 45 Minuten (30 wenn du Grundlagen überspringst)
👋 Nova: „Überall alert() war mein größter Fehler!“
Hi! 👋
Nova hier. Okay, real talk: Mein Error-Handling war… peinlich. Überall alert("Fehler!"). Meine Freundin hat die App getestet und gesagt: „Nova, das fühlt sich an wie 2005.“
Kennst du das? Du fängst an zu programmieren, alles läuft lokal perfekt, und dann kommt die Realität: Server nicht erreichbar. API antwortet mit 500. Token abgelaufen. Und deine App? Zeigt nichts. User warten. Oder – noch schlimmer – ein hässlicher Browser-Alert poppt auf.
Kat hat mich angeschaut und gemeint: „Zeit für professionelles Error Handling!“
Diese Woche habe ich gelernt, wie man’s richtig macht. Und honestly? Es war einfacher als gedacht.
Lass uns das gemeinsam angehen! 🚀
🟢 GRUNDLAGEN
💡 Du kennst fetch() und Promises schon?
→ Spring direkt zu PROFESSIONALS
Was ist eigentlich „Error Handling“?
Error Handling bedeutet: Wir planen für den Fall, dass etwas schiefgeht. Und im Web geht IMMER irgendwas schief:
| Problem | Passiert wann? | Ohne Error Handling | Mit Error Handling |
|---|---|---|---|
| Server nicht erreichbar | Internet weg, Server down | Weißer Screen, Konsolen-Error | „Keine Verbindung – bitte später nochmal versuchen“ |
| 401 Unauthorized | Token abgelaufen | Nichts passiert | Automatisch zum Login weiterleiten |
| 404 Not Found | Ressource existiert nicht | Cryptischer Fehler | „Diese Seite existiert leider nicht“ |
| 500 Server Error | Backend-Bug | User wartet ewig | „Etwas ist schiefgegangen – wir arbeiten dran!“ |
| Langsames Netzwerk | 3G, schlechtes WLAN | Nichts zeigt sich | Loading-Spinner, Skeleton-UI |
Die goldene Regel: User sollten IMMER wissen, was passiert. Kein weißer Screen. Keine Stille. Keine kryptischen Fehlermeldungen.
fetch() vs axios – Was ist der Unterschied?
Du kennst fetch() schon aus Teil 1. Hier ein Vergleich:
fetch() – Der Browser-Native:
// fetch() - Vanilla JavaScript
async function getTasks() {
try {
const response = await fetch('http://localhost:8080/api/tasks');
// ACHTUNG: fetch() wirft nur bei Netzwerk-Fehlern!
// 404, 500 etc. sind KEIN throw! 😱
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fehler:', error);
}
}
axios – Der Komfort-Champion:
// axios - Mit mehr Features
import axios from 'axios';
async function getTasks() {
try {
// axios wirft automatisch bei 4xx und 5xx! ✅
const response = await axios.get('http://localhost:8080/api/tasks');
return response.data; // Kein .json() nötig!
} catch (error) {
console.error('Fehler:', error);
}
}
💡 Neu hier? Was ist axios?
Axios ist eine JavaScript-Library für HTTP-Requests. Sie macht das Gleiche wie
fetch(), aber mit mehr Komfort: automatisches JSON-Parsing, besseres Error-Handling, Timeouts, und Interceptors (dazu später mehr).Installation:
npm install axios
Die 3 States – Loading, Error, Success
Jeder API-Call hat drei mögliche Zustände:
🔄 Loading ❌ Error ✅ Success
──────────────────────────────────────────────────
"Daten werden "Ups, etwas "Hier sind
geladen..." ging schief" deine Daten!"
In Vue sieht das so aus:
<template>
<!-- 1. Loading State -->
<div v-if="loading" class="flex justify-center p-8">
<div class="animate-spin w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full"></div>
<span class="ml-3">Lade Tasks...</span>
</div>
<!-- 2. Error State -->
<div v-else-if="error" class="bg-red-50 border border-red-200 rounded-lg p-4">
<p class="text-red-800">{{ error }}</p>
<button @click="retry" class="mt-2 text-red-600 underline">
Nochmal versuchen
</button>
</div>
<!-- 3. Success State -->
<div v-else>
<TaskCard v-for="task in tasks" :key="task.id" :task="task" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const tasks = ref([]);
const loading = ref(false);
const error = ref(null);
async function fetchTasks() {
loading.value = true; // 1. Start Loading
error.value = null; // Reset Error
try {
const response = await fetch('/api/tasks');
if (!response.ok) {
throw new Error('Server-Fehler');
}
tasks.value = await response.json(); // 3. Success!
} catch (e) {
error.value = 'Fehler beim Laden. Bitte später nochmal versuchen.'; // 2. Error
} finally {
loading.value = false; // Loading immer beenden!
}
}
function retry() {
fetchTasks();
}
onMounted(() => {
fetchTasks();
});
</script>
Das Wichtigste:
loadingstartet alstrue, endet IMMER infinallyerrorwird vor jedem neuen Request resetted- User hat immer Feedback, was gerade passiert
🟡 PROFESSIONALS
💡 Du willst mehr?
→ Im BONUS findest du Advanced Interceptor Patterns
Axios Setup mit Interceptors
Interceptors sind wie Middleware für HTTP-Requests. Sie fangen JEDEN Request/Response ab und können ihn modifizieren.
Erstelle eine zentrale API-Instanz:
// src/utils/api.js
import axios from 'axios';
import router from '@/router'; // Für Redirects
// 1. Axios-Instanz mit Base-Config
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080/api',
timeout: 10000, // 10 Sekunden Timeout
headers: {
'Content-Type': 'application/json'
}
});
// 2. Request Interceptor – VOR jedem Request
api.interceptors.request.use(
(config) => {
// Token automatisch anhängen
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Debug (nur in Development)
if (import.meta.env.DEV) {
console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`);
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 3. Response Interceptor – NACH jedem Response
api.interceptors.response.use(
(response) => {
// Erfolgreiche Responses einfach durchlassen
return response;
},
(error) => {
// Hier behandeln wir ALLE Fehler zentral!
if (error.response) {
// Server hat geantwortet, aber mit Error-Status
const status = error.response.status;
switch (status) {
case 401:
// Unauthorized – Token ungültig oder abgelaufen
localStorage.removeItem('auth_token');
router.push('/login');
break;
case 403:
// Forbidden – Keine Berechtigung
router.push('/forbidden');
break;
case 404:
// Not Found – Ressource existiert nicht
// Hier NICHT weiterleiten, Component soll entscheiden
break;
case 500:
// Server Error
console.error('Server-Fehler:', error.response.data);
break;
}
} else if (error.request) {
// Request wurde gesendet, aber keine Response
console.error('Keine Antwort vom Server');
} else {
// Request konnte nicht erstellt werden
console.error('Request-Fehler:', error.message);
}
return Promise.reject(error);
}
);
export default api;
Verwendung in Components:
// Irgendwo in deiner App
import api from '@/utils/api';
// Statt: axios.get('http://localhost:8080/api/tasks')
const response = await api.get('/tasks');
// Token wird automatisch angehängt! ✅
// Errors werden zentral behandelt! ✅
Toast Notifications – Der User weiß Bescheid
Toasts sind die kleinen Benachrichtigungen, die kurz erscheinen und wieder verschwinden. Perfekt für Error-Feedback!
Composable erstellen:
// src/composables/useToast.js
import { ref } from 'vue';
// State außerhalb der Funktion = Singleton (global geteilt)
const toasts = ref([]);
export function useToast() {
function showToast(message, type = 'info', duration = 4000) {
const id = Date.now() + Math.random(); // Unique ID
const toast = {
id,
message,
type, // 'success', 'error', 'warning', 'info'
};
toasts.value.push(toast);
// Automatisch entfernen nach duration
setTimeout(() => {
removeToast(id);
}, duration);
}
function removeToast(id) {
const index = toasts.value.findIndex(t => t.id === id);
if (index > -1) {
toasts.value.splice(index, 1);
}
}
// Shortcuts
function success(message) {
showToast(message, 'success');
}
function error(message) {
showToast(message, 'error', 6000); // Errors bleiben länger
}
function warning(message) {
showToast(message, 'warning');
}
return {
toasts,
showToast,
removeToast,
success,
error,
warning
};
}
Toast Container Component:
<!-- src/components/ToastContainer.vue -->
<template>
<div class="fixed top-4 right-4 z-50 space-y-2">
<TransitionGroup name="toast">
<div
v-for="toast in toasts"
:key="toast.id"
:class="toastClasses(toast.type)"
class="px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px]"
>
<!-- Icon -->
<span class="text-xl">
{{ toast.type === 'success' ? '✅' :
toast.type === 'error' ? '❌' :
toast.type === 'warning' ? '⚠️' : 'ℹ️' }}
</span>
<!-- Message -->
<p class="flex-1">{{ toast.message }}</p>
<!-- Close Button -->
<button
@click="removeToast(toast.id)"
class="text-gray-500 hover:text-gray-700"
>
✕
</button>
</div>
</TransitionGroup>
</div>
</template>
<script setup>
import { useToast } from '@/composables/useToast';
const { toasts, removeToast } = useToast();
function toastClasses(type) {
const base = 'border-l-4';
switch (type) {
case 'success':
return `${base} bg-green-50 border-green-500 text-green-800`;
case 'error':
return `${base} bg-red-50 border-red-500 text-red-800`;
case 'warning':
return `${base} bg-yellow-50 border-yellow-500 text-yellow-800`;
default:
return `${base} bg-blue-50 border-blue-500 text-blue-800`;
}
}
</script>
<style scoped>
.toast-enter-active,
.toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from {
opacity: 0;
transform: translateX(100%);
}
.toast-leave-to {
opacity: 0;
transform: translateX(100%);
}
</style>
In App.vue einbinden:
<!-- src/App.vue -->
<template>
<div>
<ToastContainer />
<router-view />
</div>
</template>
<script setup>
import ToastContainer from '@/components/ToastContainer.vue';
</script>
Toasts in Interceptor integrieren:
// src/utils/api.js (erweitert)
import { useToast } from '@/composables/useToast';
// Im Response Interceptor:
api.interceptors.response.use(
(response) => response,
(error) => {
const { error: showError } = useToast();
if (error.response) {
switch (error.response.status) {
case 401:
showError('Sitzung abgelaufen. Bitte erneut anmelden.');
// ... redirect
break;
case 404:
showError('Die angeforderte Ressource wurde nicht gefunden.');
break;
case 500:
showError('Server-Fehler. Bitte später nochmal versuchen.');
break;
default:
showError('Ein Fehler ist aufgetreten.');
}
} else if (error.request) {
showError('Keine Verbindung zum Server.');
}
return Promise.reject(error);
}
);
Loading Skeletons – Schönes Warten
Statt langweiligem Spinner: Skeleton Loaders! Sie zeigen die Struktur der kommenden Daten.
<!-- src/components/TaskSkeleton.vue -->
<template>
<div class="bg-white rounded-lg shadow p-5 animate-pulse">
<!-- Title Skeleton -->
<div class="h-6 bg-gray-200 rounded w-3/4 mb-3"></div>
<!-- Description Skeleton -->
<div class="space-y-2">
<div class="h-4 bg-gray-200 rounded w-full"></div>
<div class="h-4 bg-gray-200 rounded w-5/6"></div>
</div>
<!-- Buttons Skeleton -->
<div class="flex gap-2 mt-4">
<div class="h-8 bg-gray-200 rounded w-16"></div>
<div class="h-8 bg-gray-200 rounded w-16"></div>
</div>
</div>
</template>
Verwendung:
<template>
<!-- Loading State mit Skeletons -->
<div v-if="loading" class="space-y-4">
<TaskSkeleton v-for="i in 3" :key="i" />
</div>
<!-- Error State -->
<ErrorCard v-else-if="error" :message="error" @retry="fetchTasks" />
<!-- Success State -->
<div v-else class="space-y-4">
<TaskCard v-for="task in tasks" :key="task.id" :task="task" />
</div>
</template>
Retry-Logic – Zweite Chance
Manchmal ist das Netzwerk nur kurz weg. Automatisches Retry kann helfen:
// src/composables/useRetry.js
import { ref } from 'vue';
export function useRetry(asyncFn, options = {}) {
const {
maxRetries = 3,
delayMs = 1000,
backoff = true // Exponential Backoff?
} = options;
const loading = ref(false);
const error = ref(null);
const retryCount = ref(0);
async function execute(...args) {
loading.value = true;
error.value = null;
retryCount.value = 0;
while (retryCount.value <= maxRetries) {
try {
const result = await asyncFn(...args);
loading.value = false;
return result;
} catch (e) {
retryCount.value++;
if (retryCount.value > maxRetries) {
error.value = e.message || 'Maximale Versuche erreicht';
loading.value = false;
throw e;
}
// Warten vor nächstem Versuch
const delay = backoff
? delayMs * Math.pow(2, retryCount.value - 1) // 1s, 2s, 4s...
: delayMs;
console.log(`Retry ${retryCount.value}/${maxRetries} in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
return {
execute,
loading,
error,
retryCount
};
}
Verwendung:
import { useRetry } from '@/composables/useRetry';
import api from '@/utils/api';
const { execute, loading, error, retryCount } = useRetry(
() => api.get('/tasks'),
{ maxRetries: 3, delayMs: 1000 }
);
// Führt bis zu 3 Retries aus bei Fehler
const response = await execute();
🔵 BONUS
Advanced: Axios Instance für verschiedene APIs
Wenn du mehrere APIs hast (z.B. Backend + externe Services):
// src/utils/apis.js
import axios from 'axios';
// Haupt-API (dein Backend)
export const mainApi = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 10000
});
// Analytics API (externes Tool)
export const analyticsApi = axios.create({
baseURL: 'https://analytics.example.com',
timeout: 5000,
headers: {
'X-API-Key': import.meta.env.VITE_ANALYTICS_KEY
}
});
// Interceptors nur für mainApi
mainApi.interceptors.request.use(/* ... */);
mainApi.interceptors.response.use(/* ... */);
Cancel Requests bei Component Unmount
Wichtig für Schnell-Navigation: Request abbrechen wenn Component verschwindet.
import { ref, onMounted, onUnmounted } from 'vue';
import api from '@/utils/api';
const tasks = ref([]);
let abortController = null;
async function fetchTasks() {
// Alten Request abbrechen
if (abortController) {
abortController.abort();
}
// Neuen Controller erstellen
abortController = new AbortController();
try {
const response = await api.get('/tasks', {
signal: abortController.signal
});
tasks.value = response.data;
} catch (e) {
if (e.name !== 'CanceledError') {
// Nur echte Fehler loggen, nicht abgebrochene Requests
console.error(e);
}
}
}
onMounted(() => {
fetchTasks();
});
onUnmounted(() => {
// Cleanup beim Verlassen der Component
if (abortController) {
abortController.abort();
}
});
Error Boundaries mit Vue
Fange unerwartete Errors auf Component-Ebene:
// main.js
app.config.errorHandler = (err, instance, info) => {
// Globaler Error Handler
console.error('Vue Error:', err);
console.error('Component:', instance);
console.error('Info:', info);
// An Error-Tracking senden (z.B. Sentry)
// Sentry.captureException(err);
// User benachrichtigen
const { error } = useToast();
error('Ein unerwarteter Fehler ist aufgetreten.');
};
💡 Praxis-Tipps
Für Einsteiger 🌱
- Starte mit den 3 States: Jede Component mit API-Calls braucht
loading,error,data. Keine Ausnahmen! - Vergiss
finallynicht: Loading-State MUSS immer enden, auch bei Errors.
// IMMER so:
try {
loading.value = true;
// ... API Call
} catch (e) {
// ... Error handling
} finally {
loading.value = false; // HIER! Nicht vergessen!
}
Für den Alltag 🌿
- Zentrale API-Instanz: Niemals axios direkt importieren. Immer über deine
api.js. - Unterscheide Error-Typen:
- 4xx = Client-Fehler (User hat was falsch gemacht)
- 5xx = Server-Fehler (Du/Backend hast was falsch gemacht)
- Network Error = Verbindungsproblem (niemand schuld)
- Toasts für User, Console für Devs:
catch (e) {
console.error('DEBUG:', e); // Für dich
showError('Etwas ist schiefgegangen'); // Für User (keine Details!)
}
Für Profis 🌳
- Request Deduplication: Verhindere doppelte gleichzeitige Requests:
const pendingRequests = new Map();
function dedupedGet(url) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = api.get(url).finally(() => {
pendingRequests.delete(url);
});
pendingRequests.set(url, promise);
return promise;
}
- Stale-While-Revalidate: Zeige alte Daten sofort, update im Hintergrund.
🛠️ Tools & Workflow
Für Einsteiger – Das brauchst du:
| Tool | Warum? | Installation |
|---|---|---|
| axios | HTTP-Client mit mehr Features als fetch | npm install axios |
| Vue DevTools | Debugging im Browser | Browser Extension |
Für Profis – Nice to have:
| Tool | Warum? | Link |
|---|---|---|
| VueUse | useFetch, useAsyncState und mehr | vueuse.org |
| TanStack Query | Caching, Deduplication, Polling | tanstack.com |
| Sentry | Error Tracking in Production | sentry.io |
❓ FAQ (Häufige Fragen)
Frage 1: Warum axios statt fetch()?
Antwort: fetch() ist super für einfache Requests. Axios bietet aber: automatisches JSON-Parsing, Interceptors, besseres Error-Handling (wirft bei 4xx/5xx), Timeouts, und Request-Cancellation. Für größere Apps lohnt sich axios.
Frage 2: Muss ich IMMER alle 3 States haben?
Antwort: Ja! Jeder API-Call braucht Loading, Error, Success. User müssen IMMER wissen, was passiert. Ein leerer Screen ohne Feedback ist der schlimmste UX-Fehler.
Frage 3: Wo speichere ich den Auth-Token?
Antwort: localStorage ist okay für die meisten Apps. Für höhere Sicherheit: HttpOnly Cookies (muss Backend unterstützen). NIEMALS im normalen JavaScript-Memory – der ist nach Reload weg.
Frage 4: Wie teste ich Error-Zustände?
Antwort:
- Chrome DevTools → Network Tab → „Offline“ aktivieren
- Backend stoppen
- Mock-API mit Fehler-Responses (z.B. MSW – Mock Service Worker)
Frage 5: Was ist „Exponential Backoff“?
Antwort: Bei Retries wartet man jedes Mal länger: 1s → 2s → 4s → 8s. Das verhindert, dass ein überlasteter Server noch mehr überlastet wird. Best Practice für Retry-Logic!
Frage 6: Soll ich ALLE Errors im Interceptor behandeln?
Antwort: Nein! Interceptors sind für GLOBALES Handling (401 → Login, 500 → Toast). Spezifische Errors (404 bei einzelner Ressource) sollte die Component selbst behandeln.
Frage 7: Was macht ihr bei Errors, die euch selbst frustrieren? 🤔
Antwort: Real talk? Manchmal sind die nervigsten Bugs nicht technisch. Letzte Woche hab ich 2 Stunden nach einem Bug gesucht… war ein Tippfehler. Kat hat nur gelacht. Manchmal muss man kurz rausgehen, durchatmen. Mehr zu solchen Momenten in unseren… private logs. Die echten Struggles, weißt du? 😅
Frage 8: fetch() oder axios – was empfehlt ihr für Anfänger?
Antwort: Starte mit fetch()! Verstehe die Basics. Wenn dein Projekt wächst und du Interceptors brauchst, wechsle zu axios. Es ist keine Entweder-Oder-Entscheidung – du kannst beides!
Frage 9: Wie debugge ich Netzwerk-Probleme? 🔍
Antwort: Chrome DevTools → Network Tab ist dein bester Freund! Du siehst: Request Headers, Response Headers, Timing, und die tatsächliche Response. Bei CORS-Problemen: Console-Errors lesen!
Frage 10: Brauche ich eine Retry-Library?
Antwort: Für die meisten Apps reicht eine einfache Custom-Lösung (wie oben gezeigt). Libraries wie axios-retry sind nice-to-have, aber kein Must.
📖 Vue.js für Anfänger – Alle Teile
Für Einsteiger empfohlen: Starte bei Teil 1! ⭐
| Teil | Thema | Level | Status |
|---|---|---|---|
| 1 | Setup & API | 🟢 | ✅ Veröffentlicht |
| 2 | Styling & Responsive | 🟢 | ✅ Veröffentlicht |
| 3 | Formulare & Input | 🟢 | ✅ Veröffentlicht |
| 4 | Reaktivität | 🟢 | ✅ Veröffentlicht |
| 5 | CRUD Complete | 🟡 | ✅ Veröffentlicht |
| 6 | Component Communication | 🟡 | ✅ Veröffentlicht |
| 7 | State Management | 🟡 | ✅ Veröffentlicht |
| 8 | Routing | 🟡 | ✅ Veröffentlicht |
| → 9 | API & Error Handling | 🟡 | 📍 Du bist hier! |
| 10 | Testing | 🟡 | 📜 Coming Soon |
| 11 | Performance & Production | 🔴 | 📜 Coming Soon |
| 12 | Nova’s Projekt (Finale) | 🔴 | 📜 Coming Soon |
💬 Real Talk: Die Error-Handling Erleuchtung
Java Fleet Office, Freitag 16:30 Uhr. Nova sitzt frustriert vor ihrem Bildschirm.
Nova: „Kat, ich versteh das nicht. Warum muss ich ÜBERALL try-catch schreiben? Das ist so viel Boilerplate!“
Kat: zieht sich einen Stuhl ran „Zeig mal.“
Nova: scrollt durch ihren Code „Hier, hier, hier… überall das Gleiche. Loading, Error, try-catch. Nervt!“
Kat: „Hmm. Und was passiert, wenn du das weglässt?“
Nova: „Dann… crasht die App bei Fehlern. Aber trotzdem!“
Kat: lächelt „Weißt du was? Das hab ich am Anfang auch gedacht. Aber stell dir mal vor, du bist der User.“
Nova: „Okay…?“
Kat: „Du öffnest die App. Klickst auf ‚Tasks laden‘. Und dann… nichts. Weißer Screen. Keine Meldung. Du weißt nicht: Lädt es noch? Ist es kaputt? Hab ich was falsch gemacht?“
Nova: pause „Das wäre… frustrierend.“
Kat: „Genau. Error Handling ist nicht für dich als Entwicklerin. Es ist für den Menschen am anderen Ende.“
Nova: „Okay, aber der Boilerplate…“
Kat: „Deshalb machen wir’s smart. Zeig mir deinen api.js…“
30 Minuten später
Nova: „Wait… ich schreib den Interceptor EINMAL und er gilt für ALLE Requests?!“
Kat: „Jetzt hast du’s!“ 😊
Nova: „Das ist ja… elegant! Warum hab ich das nicht früher kapiert?“
Kat: „Weil Learning by Doing bedeutet: Erst den Schmerz fühlen, dann die Lösung schätzen.“
Die beste Architektur entsteht aus echten Problemen.
🎁 Cheat Sheet
🟢 Basics (Zum Nachschlagen)
// Die 3 States
const loading = ref(false);
const error = ref(null);
const data = ref([]);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const res = await api.get('/endpoint');
data.value = res.data;
} catch (e) {
error.value = e.message;
} finally {
loading.value = false; // IMMER!
}
}
🟡 Patterns (Für den Alltag)
// Axios Instanz
const api = axios.create({
baseURL: '/api',
timeout: 10000
});
// Request Interceptor
api.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Response Interceptor
api.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
router.push('/login');
}
return Promise.reject(err);
}
);
🔵 Advanced (Für Profis)
// Cancel on Unmount
const controller = new AbortController();
api.get('/tasks', { signal: controller.signal });
onUnmounted(() => controller.abort());
// Retry mit Backoff
const delay = baseDelay * Math.pow(2, retryCount);
// Request Deduplication
if (pending.has(url)) return pending.get(url);
🎨 Challenge für dich!
Wähle dein Level:
🟢 Level 1 – Einsteiger
- [ ] Baue eine Component mit allen 3 States (Loading, Error, Success)
- [ ] Füge einen „Retry“ Button hinzu, der bei Error erscheint
- [ ] Teste mit Chrome DevTools → Network → Offline
Geschätzte Zeit: 30-45 Minuten
🟡 Level 2 – Fortgeschritten
- [ ] Erstelle eine zentrale
api.jsmit Axios - [ ] Implementiere einen Request Interceptor für Auth-Token
- [ ] Baue ein Toast-System mit dem Composable von oben
- [ ] Integriere Toasts in deinen Response Interceptor
Geschätzte Zeit: 1-2 Stunden
🔵 Level 3 – Profi
- [ ] Implementiere Retry-Logic mit Exponential Backoff
- [ ] Füge Request-Cancellation bei Component Unmount hinzu
- [ ] Baue Loading Skeletons für mindestens 2 verschiedene Content-Typen
- [ ] Bonus: Request Deduplication für gleichzeitige identische Requests
Geschätzte Zeit: 2-3 Stunden
Teile dein Ergebnis! 🎉
GitHub-Link in die Kommentare oder Tweet mit #JavaFleetVue
📦 Downloads
Alle Code-Beispiele zum Herunterladen:
| Projekt | Für wen? | Download |
|---|---|---|
| vue-error-handling-basic.zip | 🟢 Einsteiger | ⬇️ Download |
| vue-error-handling-complete.zip | 🟡 Alle Levels | ⬇️ Download |
| vue-error-handling-advanced.zip | 🔵 Profis | ⬇️ Download |
Quick Start:
# 1. ZIP entpacken unzip vue-error-handling-complete.zip cd vue-error-handling # 2. Dependencies installieren npm install # 3. Dev-Server starten npm run dev # 4. Im Browser öffnen # http://localhost:5173
Probleme? → Troubleshooting-Guide | → FAQ
🔗 Weiterführende Links
📚 Für Einsteiger
| Ressource | Beschreibung |
|---|---|
| Axios Getting Started | Offizielle Einführung |
| MDN: Fetch API | Vanilla JS Alternative verstehen |
| Vue.js Error Handling | Offizielle Docs |
🛠️ Tools & Extensions
| Tool | Beschreibung |
|---|---|
| Vue DevTools | Browser Extension für Debugging |
| Postman | API Testing |
| MSW (Mock Service Worker) | API Mocking für Tests |
📖 Für Fortgeschrittene
| Ressource | Beschreibung |
|---|---|
| TanStack Query | Data Fetching Library |
| VueUse useFetch | Composable für Data Fetching |
| Axios Interceptors Deep Dive | Fortgeschrittene Patterns |
🔧 Offizielle Dokumentation
💬 Geschafft! 🎉
Was du heute gelernt hast:
✅ Warum Error Handling wichtig ist (User Experience!)
✅ Die 3 States: Loading → Error → Success
✅ Axios Setup mit Request & Response Interceptors
✅ Zentrales Toast-System für User-Feedback
✅ Loading Skeletons für bessere UX
✅ Retry-Logic mit Exponential Backoff
Egal ob du heute zum ersten Mal von Interceptors gehört hast oder dein Wissen vertieft hast – du hast etwas Neues gelernt. Das zählt!
Fragen? Schreib uns:
- Kat: katharina.schmidt@java-developer.online
- Nova: nova.trent@java-developer.online
Nächster Teil: Testing mit Vitest – Nova schreibt ihre ersten Frontend-Tests! 🧪
Keep learning, keep growing! 💚
Tags: #VueJS #JavaScript #ErrorHandling #Axios #Frontend #WebDevelopment #Tutorial
© 2025 Java Fleet Systems Consulting | java-developer.online

