Skip to content

Kapitel 38: axios (Teil 3) - CORS & Fehlerbehandlung

📙 Lernziel: CORS verstehen und lösen!


38.1 Was ist CORS?

CORS (Cross-Origin Resource Sharing) ist eine Sicherheitsrichtlinie von Browsern.

Problem:

Frontend: http://localhost:5173
Backend:  http://api.example.com:3000

                Anderer Origin = CORS Fehler!

Fehlermeldung:

Access to XMLHttpRequest at 'http://api.example.com' 
from origin 'http://localhost:5173' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

38.2 CORS lösen (Backend)

Lösung 1: Backend konfigurieren (Empfohlen!)

Node.js (Express):

javascript
// server.js
const express = require('express')
const app = express()

// CORS Middleware
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:5173')
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
  next()
})

// Oder mit cors Bibliothek
const cors = require('cors')
app.use(cors({
  origin: 'http://localhost:5173',
  credentials: true
}))

app.listen(3000, () => {
  console.log('Server läuft auf Port 3000')
})

Spring Boot (Java):

java
// CorsConfig.java
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:5173");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

38.3 CORS lösen (Frontend - Proxy)

Lösung 2: Proxy in Vite konfigurieren (Entwicklungsumgebung!)

Vite Konfiguration (vite.config.js):

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      // API Anfragen proxyen
      '/api': {
        target: 'http://api.example.com:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

Verwendung:

vue
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const data = ref(null)

onMounted(async () => {
  // Statt: http://api.example.com:3000/users
  // Nutze: /api/users (wird zu localhost:5173/api/users)
  const response = await axios.get('/api/users')
  data.value = response.data
})
</script>

<template>
  <div>
    <h2>Benutzer</h2>
    <ul>
      <li v-for="user in data" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
  </div>
</template>

38.4 CORS lösen (JSONP - Veraltet!)

Lösung 3: JSONP (nur für GET, veraltet!)

vue
<script setup>
import { ref, onMounted } from 'vue'

const data = ref(null)

onMounted(() => {
  // JSONP - Funktioniert nur mit GET
  const script = document.createElement('script')
  script.src = 'http://api.example.com:3000/users?callback=handleData'
  document.body.appendChild(script)
  
  // Global Funktion
  window.handleData = (response) => {
    data.value = response
  }
})
</script>

Nachteile:

  • ❌ Nur GET Requests
  • ❌ Sicherheitsrisiken (XSS)
  • ❌ Veraltet (nutze lieber CORS oder Proxy)

38.5 Fehlerbehandlung (Global)

Globales Fehlerhandling mit axios Interceptors:

javascript
// utils/apiClient.js
import axios from 'axios'
import { useRouter } from 'vue-router'

const apiClient = axios.create({
  baseURL: 'http://localhost:3000',
  timeout: 5000
})

// Response Interceptor (Fehlerbehandlung)
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    const router = useRouter()
    
    if (error.response) {
      // Server hat geantwortet, aber mit Fehler-Status
      const { status } = error.response
      
      if (status === 401) {
        // Unauthorized - Token abgelaufen
        localStorage.removeItem('token')
        router.push('/login')
      } else if (status === 403) {
        // Forbidden - Keine Berechtigung
        alert('Keine Berechtigung für diese Aktion!')
      } else if (status === 404) {
        // Not Found
        router.push('/404')
      } else if (status === 500) {
        // Serverfehler
        alert('Serverfehler! Bitte später erneut versuchen.')
      }
    } else if (error.request) {
      // Keine Antwort vom Server (Netzwerkfehler)
      alert('Keine Verbindung zum Server! Bitte Internetverbindung prüfen.')
    } else {
      // Fehler beim Request-Setup
      console.error('Request Fehler:', error.message)
    }
    
    return Promise.reject(error)
  }
)

export default apiClient

38.6 Übung: API Client mit Fehlerbehandlung

Aufgabe: Erstelle einen API Client mit CORS-Proxy und globalem Fehlerhandling.

Lösung:

1. Vite Konfiguration (vite.config.js):

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

2. API Client erstellen (utils/api.js):

javascript
// utils/api.js
import axios from 'axios'
import { useRouter } from 'vue-router'

const apiClient = axios.create({
  baseURL: '/api',  // Nutzt Vite Proxy!
  timeout: 5000
})

// Request Interceptor (Token hinzufügen)
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => Promise.reject(error)
)

// Response Interceptor (Fehlerbehandlung)
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response) {
      const { status } = error.response
      
      if (status === 401) {
        localStorage.removeItem('token')
        window.location.href = '/login'
      }
    } else if (error.request) {
      alert('Keine Verbindung zum Server!')
    }
    
    return Promise.reject(error)
  }
)

export default apiClient

3. Verwendung in Komponente:

vue
<!-- views/PostsView.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import apiClient from '../utils/api.js'

const posts = ref([])
const isLoading = ref(false)
const error = ref(null)

const fetchPosts = async () => {
  isLoading.value = true
  error.value = null
  
  try {
    const response = await apiClient.get('/posts?_limit=5')
    posts.value = response.data
  } catch (err) {
    error.value = err.message
  } finally {
    isLoading.value = false
  }
}

onMounted(fetchPosts)
</script>

<template>
  <div class="posts-view">
    <h2>Posts</h2>
    
    <div v-if="isLoading">Lädt...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <ul v-else>
      <li v-for="post in posts" :key="post.id">
        <h3>{{ post.title }}</h3>
        <p>{{ post.body }}</p>
      </li>
    </ul>
    
    <button @click="fetchPosts()" :disabled="isLoading">
      {{ isLoading ? 'Lädt...' : 'Neu laden' }}
    </button>
  </div>
</template>

<style scoped>
.posts-view {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.error {
  color: red;
  padding: 10px;
  background: #ffebee;
  border-radius: 4px;
}

button {
  padding: 10px 20px;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

button:disabled {
  background: #ccc;
  cursor: not-allowed;
}
</style>

✅ Zusammenfassung

In diesem Kapitel hast du gelernt:

  • ✅ Was CORS ist (Cross-Origin Resource Sharing)
  • ✅ CORS lösen (Backend konfigurieren)
  • ✅ CORS lösen (Frontend Proxy mit Vite)
  • ✅ CORS lösen (JSONP - veraltet!)
  • ✅ Globales Fehlerhandling mit Interceptors
  • ✅ Praxis: API Client mit Fehlerbehandlung

🎯 Nächster Schritt: In Kapitel 39 lernst du fortgeschrittene Vue 3 Konzepte!


← Zurück zu Kapitel 37: axios Instanz & InterceptorsWeiter zu Kapitel 39: Fortgeschrittene Konzepte →

Frei für alle Anfänger