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:

ProblemPassiert wann?Ohne Error HandlingMit Error Handling
Server nicht erreichbarInternet weg, Server downWeißer Screen, Konsolen-Error„Keine Verbindung – bitte später nochmal versuchen“
401 UnauthorizedToken abgelaufenNichts passiertAutomatisch zum Login weiterleiten
404 Not FoundRessource existiert nichtCryptischer Fehler„Diese Seite existiert leider nicht“
500 Server ErrorBackend-BugUser wartet ewig„Etwas ist schiefgegangen – wir arbeiten dran!“
Langsames Netzwerk3G, schlechtes WLANNichts zeigt sichLoading-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

→ Mehr zu axios in der offiziellen Doku

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:

  • loading startet als true, endet IMMER in finally
  • error wird 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 🌱

  1. Starte mit den 3 States: Jede Component mit API-Calls braucht loading, error, data. Keine Ausnahmen!
  2. Vergiss finally nicht: 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 🌿

  1. Zentrale API-Instanz: Niemals axios direkt importieren. Immer über deine api.js.
  2. 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)
  3. 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 🌳

  1. 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;
}
  1. Stale-While-Revalidate: Zeige alte Daten sofort, update im Hintergrund.

🛠️ Tools & Workflow

Für Einsteiger – Das brauchst du:

ToolWarum?Installation
axiosHTTP-Client mit mehr Features als fetchnpm install axios
Vue DevToolsDebugging im BrowserBrowser Extension

Für Profis – Nice to have:

ToolWarum?Link
VueUseuseFetch, useAsyncState und mehrvueuse.org
TanStack QueryCaching, Deduplication, Pollingtanstack.com
SentryError Tracking in Productionsentry.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! ⭐

TeilThemaLevelStatus
1Setup & API🟢✅ Veröffentlicht
2Styling & Responsive🟢✅ Veröffentlicht
3Formulare & Input🟢✅ Veröffentlicht
4Reaktivität🟢✅ Veröffentlicht
5CRUD Complete🟡✅ Veröffentlicht
6Component Communication🟡✅ Veröffentlicht
7State Management🟡✅ Veröffentlicht
8Routing🟡✅ Veröffentlicht
→ 9API & Error Handling🟡📍 Du bist hier!
10Testing🟡📜 Coming Soon
11Performance & Production🔴📜 Coming Soon
12Nova’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.js mit 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:

ProjektFü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

RessourceBeschreibung
Axios Getting StartedOffizielle Einführung
MDN: Fetch APIVanilla JS Alternative verstehen
Vue.js Error HandlingOffizielle Docs

🛠️ Tools & Extensions

ToolBeschreibung
Vue DevToolsBrowser Extension für Debugging
PostmanAPI Testing
MSW (Mock Service Worker)API Mocking für Tests

📖 Für Fortgeschrittene

RessourceBeschreibung
TanStack QueryData Fetching Library
VueUse useFetchComposable für Data Fetching
Axios Interceptors Deep DiveFortgeschrittene 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

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.