Claude Code Axios: HTTP-Requests, Interceptors und API-Integration mit KI

Axios ist die meistgenutzte HTTP-Bibliothek im JavaScript-Ökosystem — und gleichzeitig eine, bei der die wirkliche Produktivität erst beginnt, wenn man die fortgeschrittenen Features beherrscht: Interceptors, axios.create() mit gemeinsamer Konfiguration, Retry-Logik bei Netzwerkfehlern, korrektes TypeScript-Typing. Genau dort verlieren viele Teams Stunden.

Claude Code Axios verändert diesen Prozess grundlegend. Nicht weil KI magisch Code schreibt, sondern weil Claude Code den gesamten Projekt-Kontext sieht: bestehende API-Klassen, verwendete Typen, aktuelle Fehlerbehandlungsmüster — und daraus passgenaue Axios-Integrationen generiert, die tatsächlich in das Projekt passen statt generische Boilerplate zu liefern.

Dieser Artikel zeigt, wie das konkret funktioniert — von einfachen GET-Requests bis zu produktionsreifen HTTP-Clients mit Interceptors, AbortController, FormData-Upload und vollständiger Testabdeckung per axios-mock-adapter.

Claude Code Kurs — API-Integration, Agents und Workflows auf Deutsch

Axios ist nur ein Teil: der Kurs zeigt, wie du Claude Code für komplette Entwicklungsworkflows einsetzt — von der API-Schicht bis zu autonomen Agents. Einmalig bezahlt, kein Abo.

Zum Kurs — ab €29 → Basis €29 · Pro €49 · Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht

Warum Axios statt fetch()?

Die Frage kommt regelmäßig: Warum Axios, wenn der Browser doch fetch() eingebaut hat? Die ehrliche Antwort ist nicht "weil Axios besser ist" — sondern weil Axios für bestimmte Anwendungsfälle weniger Boilerplate erzeugt und mehr eingebaut hat.

Wann fetch() die bessere Wahl ist: bei simplen One-off-Requests ohne Interceptor-Bedarf, bei Projekten die keine externen Dependencies wollen, oder wenn Bundle-Größe kritisch ist. Axios wiegt ~14 KB gzipped — für die meisten Anwendungen kein Problem, aber es lohnt sich, es zu wissen.

Grundlagen: axios.get, post, put, delete

Claude Code versteht Axios-Projekte sofort. Ein typischer Prompt für den Einstieg:

"Zeig mir, wie ich GET, POST, PUT und DELETE gegen unsere REST-API unter api.example.com/v1 implementiere. Fehlerbehandlung soll AxiosError nutzen, Typen sollen aus src/types/api.ts kommen."

Claude Code liest src/types/api.ts, versteht die vorhandenen Interfaces und generiert passende Aufrufe:

import axios, { AxiosError } from 'axios';
import type { User, CreateUserDto, UpdateUserDto } from '../types/api';

const BASE_URL = 'https://api.example.com/v1';

// GET: einzelner User
async function getUser(id: string): Promise<User> {
  const { data } = await axios.get<User>(`${BASE_URL}/users/${id}`);
  return data;
}

// GET: Liste mit Query-Params
async function getUsers(page = 1, limit = 20): Promise<User[]> {
  const { data } = await axios.get<User[]>(`${BASE_URL}/users`, {
    params: { page, limit },
  });
  return data;
}

// POST: neuen User anlegen
async function createUser(dto: CreateUserDto): Promise<User> {
  const { data } = await axios.post<User>(`${BASE_URL}/users`, dto);
  return data;
}

// PUT: vollstaendige Aktualisierung
async function updateUser(id: string, dto: UpdateUserDto): Promise<User> {
  const { data } = await axios.put<User>(`${BASE_URL}/users/${id}`, dto);
  return data;
}

// DELETE: User entfernen
async function deleteUser(id: string): Promise<void> {
  await axios.delete(`${BASE_URL}/users/${id}`);
}

// Fehlerbehandlung mit AxiosError
function handleAxiosError(error: unknown): never {
  if (axios.isAxiosError(error)) {
    const axiosError = error as AxiosError<{ message: string }>;
    const status = axiosError.response?.status;
    const message = axiosError.response?.data?.message ?? axiosError.message;
    throw new Error(`API-Fehler ${status}: ${message}`);
  }
  throw error;
}

Der entscheidende Unterschied zu generischem Code: Claude Code nutzt die tatsächlichen Typen aus dem Projekt. Wenn User schon ein Interface mit 15 Feldern ist, erfindet Claude Code keine Parallelstruktur — es verwendet das Bestehende.

axios.create() — ein HTTP-Client für das gesamte Projekt

Der häufigste Fehler in Axios-Projekten: jeder API-Aufruf konfiguriert selbst baseURL, Timeout und Auth-Header. Das funktioniert für die ersten 20 Requests — beim 50. ist die Konfiguration an 15 Stellen verteilt und kaum mehr konsistent zu halten.

Die Lösung ist axios.create(), das eine Axios-Instanz mit gemeinsamer Basiskonfiguration erzeugt:

// src/lib/apiClient.ts
import axios from 'axios';

export const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL ?? 'https://api.example.com/v1',
  timeout: 10_000,          // 10 Sekunden globaler Timeout
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-App-Version': '2.1.0',
  },
  withCredentials: true,    // Cookies bei Cross-Origin-Requests mitsenden
});

Alle API-Aufrufe importieren danach apiClient statt des globalen axios. Claude Code erkennt dieses Muster, wenn es in einem Projekt bereits existiert — und hält es konsequent durch, statt bei neuen Funktionen plötzlich wieder axios.get() direkt aufzurufen.

Tipp: Mehrere Clients für verschiedene APIs. In Projekten mit mehreren Backend-Diensten lohnt es sich, pro Dienst eine eigene Axios-Instanz anzulegen: authClient, paymentClient, analyticsClient. Jeder bringt seine eigene baseURL und Auth-Strategie mit. Claude Code hält diese Trennung sauber — vorausgesetzt, man sagt ihm beim ersten Prompt, wie die Architektur aussieht.

Request- und Response-Interceptors

Interceptors sind das Herzstück produktionsreifer Axios-Integrationen. Sie erlauben es, jeden Request und jede Response global zu transformieren, bevor der eigentliche Code sie sieht.

Request Interceptor: Auth-Token automatisch anfügen

// Token aus dem Store lesen und bei jedem Request anfuegen
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('access_token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

Response Interceptor: Token-Refresh bei 401

Das klassische Muster für JWT-basierte Auth: wenn der Server 401 zurückgibt, neues Access-Token anfordern und den ursprünglichen Request wiederholen.

let isRefreshing = false;
let refreshQueue: Array<(token: string) => void> = [];

apiClient.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config!;

    if (error.response?.status !== 401) {
      return Promise.reject(error);
    }

    // Verhindert dass mehrere gleichzeitige Requests je einen Refresh ausloesen
    if (isRefreshing) {
      return new Promise((resolve) => {
        refreshQueue.push((token: string) => {
          originalRequest.headers.Authorization = `Bearer ${token}`;
          resolve(apiClient(originalRequest));
        });
      });
    }

    isRefreshing = true;

    try {
      const { data } = await axios.post('/auth/refresh', {
        refresh_token: localStorage.getItem('refresh_token'),
      });

      const newToken = data.access_token;
      localStorage.setItem('access_token', newToken);

      // Alle wartenden Requests mit neuem Token durchlassen
      refreshQueue.forEach((cb) => cb(newToken));
      refreshQueue = [];

      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return apiClient(originalRequest);
    } catch (refreshError) {
      // Refresh fehlgeschlagen: User ausloggen
      localStorage.clear();
      window.location.href = '/login';
      return Promise.reject(refreshError);
    } finally {
      isRefreshing = false;
    }
  }
);

Diesen Code von Hand fehlerfrei zu schreiben ist mühsam — die Race Condition zwischen parallelen 401-Requests ist ein bekannter Fallstrick. Claude Code implementiert das Muster korrekt, weil es über die Problematik trainiert wurde und das Queue-Pattern kennt.

Error Handling mit AxiosError

Axios unterscheidet drei Fehlertypen, die man kennen muss:

import axios, { AxiosError } from 'axios';

function classifyAxiosError(error: unknown): {
  type: 'response' | 'network' | 'setup' | 'unknown';
  status?: number;
  message: string;
} {
  if (!axios.isAxiosError(error)) {
    return { type: 'unknown', message: String(error) };
  }

  const err = error as AxiosError<{ error: string }>;

  if (err.response) {
    return {
      type: 'response',
      status: err.response.status,
      message: err.response.data?.error ?? err.message,
    };
  }

  if (err.request) {
    return {
      type: 'network',
      message: `Keine Antwort vom Server (${err.code ?? 'UNKNOWN'})`,
    };
  }

  return {
    type: 'setup',
    message: err.message,
  };
}

Wichtig: axios.isAxiosError(error) prüfen, bevor man auf error.response zugreift. TypeScript weiß ohne diese Prüfung nicht, ob error ein AxiosError ist — und error.response?.status ohne die Guard-Clause ist eine potenzielle Laufzeit-Exception bei nicht-Axios-Fehlern.

Timeout-Konfiguration

Timeouts sind in produktivem Code nicht optional. Ein Request ohne Timeout kann theoretisch ewig hängen und Ressourcen blockieren. Axios macht es einfach:

// Global fuer alle Requests der Instanz
const apiClient = axios.create({
  timeout: 10_000, // 10 Sekunden
});

// Oder pro Request ueberschreiben
const { data } = await apiClient.get('/heavy-report', {
  timeout: 60_000, // 60 Sekunden fuer lange Berechnungen
});

// Timeout-Fehler identifizieren
apiClient.interceptors.response.use(
  (response) => response,
  (error: AxiosError) => {
    if (error.code === 'ECONNABORTED') {
      throw new Error('Request-Timeout: Server antwortet zu langsam');
    }
    return Promise.reject(error);
  }
);

Claude Code setzt Timeouts konsequent, wenn man es im ersten Prompt oder in der apiClient.ts definiert. Ohne explizite Erwähnung übernimmt es das Muster aus dem bestehenden Code — oder fragt nach, wenn keins vorhanden ist.

Request-Abbruch mit AbortController

Bei Suchfeldern, Autocomplete oder Tab-Wechseln braucht man die Möglichkeit, laufende Requests abzubrechen. Axios unterstützt seit v0.22 den nativen AbortController der Web-API:

// React-Beispiel: Request bei Unmount abbrechen
import { useEffect, useState } from 'react';
import { apiClient } from '../lib/apiClient';

function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    apiClient
      .get<User>(`/users/${userId}`, {
        signal: controller.signal,
      })
      .then(({ data }) => setUser(data))
      .catch((error) => {
        // Abbruch-Fehler nicht als echten Fehler behandeln
        if (!axios.isCancel(error)) {
          console.error('Fehler beim Laden des Users:', error);
        }
      });

    // Cleanup: Request abbrechen wenn Komponente unmountet
    return () => controller.abort();
  }, [userId]);

  return user;
}

Das ältere CancelToken-Muster von Axios ist seit v0.22 deprecated. Claude Code verwendet AbortController, wenn man es nach dem richtigen Muster fragt — wichtig ist, den Kontext zu geben: React, Vue, plain JavaScript oder Node.js, weil sich die Cleanup-Mechanismen unterscheiden.

Multipart/FormData und Datei-Upload

Datei-Uploads mit Fortschrittsanzeige sind mit plain fetch() mühsam. Axios macht es deutlich einfacher:

async function uploadFile(
  file: File,
  onProgress: (percent: number) => void
): Promise<{ url: string; filename: string }> {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('category', 'profile-image');

  const { data } = await apiClient.post<{ url: string; filename: string }>(
    '/uploads',
    formData,
    {
      // Content-Type NICHT manuell setzen!
      // Axios erkennt FormData und setzt multipart/form-data mit korrektem boundary
      onUploadProgress: (progressEvent) => {
        if (progressEvent.total) {
          const percent = Math.round(
            (progressEvent.loaded / progressEvent.total) * 100
          );
          onProgress(percent);
        }
      },
      timeout: 120_000, // 2 Minuten fuer grosse Dateien
    }
  );

  return data;
}

Wichtig bei FormData: Den Content-Type-Header nicht manuell auf multipart/form-data setzen. Axios (und der Browser) setzen ihn automatisch und fügen dabei den korrekten boundary-Parameter an — ohne den kann der Server den Body nicht parsen. Wird Content-Type manuell gesetzt, fehlt dieser Parameter.

Retry-Logik bei transienten Fehlern

Netzwerkfehler sind oft temporär. Ein 503 oder Timeout bedeutet oft nur, dass der Server kurz überlastet war — ein Retry nach 1-2 Sekunden würde funktionieren. Axios hat keine eingebaute Retry-Logik, aber sie lässt sich sauber per Interceptor implementieren:

interface RetryConfig {
  maxRetries: number;
  retryDelay: number;       // Basisverzoegerung in ms
  retryableStatuses: number[];
}

function addRetryInterceptor(
  client: typeof apiClient,
  config: RetryConfig = {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  }
) {
  client.interceptors.response.use(
    (response) => response,
    async (error: AxiosError) => {
      const originalRequest = error.config as typeof error.config & {
        _retryCount?: number;
      };

      if (!originalRequest) return Promise.reject(error);

      const retryCount = originalRequest._retryCount ?? 0;
      const status = error.response?.status;

      const shouldRetry =
        retryCount < config.maxRetries &&
        (error.code === 'ECONNABORTED' ||
          (status !== undefined && config.retryableStatuses.includes(status)));

      if (!shouldRetry) return Promise.reject(error);

      originalRequest._retryCount = retryCount + 1;

      // Exponentielles Backoff: 1s, 2s, 4s
      const delay = config.retryDelay * Math.pow(2, retryCount);
      await new Promise((resolve) => setTimeout(resolve, delay));

      return client(originalRequest);
    }
  );
}

Claude Code generiert diese Retry-Logik auf Anfrage und passt die retryableStatuses an den spezifischen API-Server an. Wichtig: 401 und 403 sind keine Retry-Kandidaten — Auth-Fehler brauchen Token-Refresh, kein blindes Wiederholen.

TypeScript-Typisierung mit Axios

Axios hat ausgezeichnete TypeScript-Unterstützung eingebaut. Die Generics-Parameter axios.get<T>() typisieren response.data direkt:

// Typen fuer API-Responses definieren
interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  hasMore: boolean;
}

interface ApiError {
  error: string;
  code: string;
  details?: Record<string, string[]>;
}

// Vollstaendig typisierte API-Funktion
async function getProducts(
  params: { page?: number; category?: string; search?: string }
): Promise<PaginatedResponse<Product>> {
  const { data } = await apiClient.get<PaginatedResponse<Product>>(
    '/products',
    { params }
  );
  return data;
}

// AxiosResponse-Typ fuer vollstaendigen Zugriff auf Headers etc.
import type { AxiosResponse } from 'axios';

async function getProductWithMeta(id: string): Promise<{
  product: Product;
  etag: string | null;
}> {
  const response: AxiosResponse<Product> = await apiClient.get(`/products/${id}`);
  return {
    product: response.data,
    etag: response.headers['etag'] ?? null,
  };
}

Claude Code nutzt die vorhandenen Types aus dem Projekt automatisch — wenn Product schon in src/types/ definiert ist, importiert es das, statt ein neues Interface zu erfinden.

Testing mit axios-mock-adapter

axios-mock-adapter ist das Standardwerkzeug, um Axios-Requests in Tests zu intercepten, ohne echte HTTP-Calls zu machen:

// Installation: npm install -D axios-mock-adapter

import MockAdapter from 'axios-mock-adapter';
import { apiClient } from '../lib/apiClient';
import { getUser, createUser } from '../services/userService';

describe('userService', () => {
  let mock: MockAdapter;

  beforeEach(() => {
    mock = new MockAdapter(apiClient, { delayResponse: 0 });
  });

  afterEach(() => {
    mock.reset();
  });

  afterAll(() => {
    mock.restore();
  });

  it('gibt einen User per ID zurueck', async () => {
    const mockUser: User = { id: '1', name: 'Alice', email: 'alice@example.com' };
    mock.onGet('/users/1').reply(200, mockUser);

    const user = await getUser('1');
    expect(user).toEqual(mockUser);
  });

  it('wirft bei 404 einen Fehler', async () => {
    mock.onGet('/users/999').reply(404, { error: 'User not found' });

    await expect(getUser('999')).rejects.toThrow('API-Fehler 404');
  });

  it('sendet POST mit korrekten Daten', async () => {
    const dto: CreateUserDto = { name: 'Bob', email: 'bob@example.com' };
    const created: User = { id: '2', ...dto };

    mock.onPost('/users').reply(201, created);

    const result = await createUser(dto);
    expect(result.id).toBe('2');
    expect(mock.history.post[0].data).toBe(JSON.stringify(dto));
  });

  it('testet Netzwerkfehler', async () => {
    mock.onGet('/users/1').networkError();
    await expect(getUser('1')).rejects.toThrow();
  });

  it('testet Timeout', async () => {
    mock.onGet('/users/1').timeout();
    await expect(getUser('1')).rejects.toThrow();
  });
});

Claude Code generiert diese Tests auf Basis des tatsächlichen Service-Codes. Der wichtigste Prompt dafür:

"Schreib Tests fuer alle Funktionen in src/services/userService.ts mit axios-mock-adapter. Happy path, 404, 500 und Timeout pro Funktion. Mock-Adapter soll pro Test zurueckgesetzt werden."

fetch() vs. Axios — Entscheidungshilfe

Für die Entscheidungsfindung eine sachliche Gegenüberstellung:

Empfehlung: Axios für Projekte mit mehreren API-Endpunkten, Auth-Interceptors und Retry-Logik. fetch() für einfache Projekte mit wenigen API-Calls, bei denen Bundle-Größe zählt oder externe Dependencies vermieden werden sollen.

Praktische Prompts für Claude Code Axios

Die Qualität von Claude Code hängt stark vom Prompt-Kontext ab. Diese Prompts produzieren konsistent guten Output:

Typische Fehler — und wie Claude Code sie vermeidet

Einige Muster, die in Axios-Projekten regelmäßig Probleme machen:

Fazit: Claude Code als Axios-Partner

Die eigentliche Stärke von Claude Code Axios liegt nicht darin, Code-Snippets zu generieren — die findet man auch bei Stack Overflow. Der Unterschied ist der Kontext: Claude Code liest den bestehenden Code, versteht die Architektur, kennt die vorhandenen Typen und generiert Axios-Integrationen, die konsistent mit dem Projekt sind.

Das bedeutet: kein Erfinden von Typen, die schon existieren. Kein Ignorieren von bereits definierten Error-Handling-Mustern. Kein Einführen eines sechsten Stils für API-Calls in einem Projekt, das fünf verschiedene schon hat.

Der beste Einstieg ist ein klarer erster Prompt mit dem gesamten Kontext: welche API-Endpunkte, welche Auth-Strategie, welche TypeScript-Types, welches Testing-Framework. Danach arbeitet Claude Code konsistent innerhalb dieser Struktur — und lässt sich bei jedem weiteren Schritt präzise steuern.

Den ganzen Workflow mit Claude Code lernen

Axios ist Kapitel drei. Der Kurs führt durch API-Layer, Agents, Hooks und produktive Entwicklungsworkflows auf Deutsch — von der ersten Installation bis zum autonomen Agent im Produktivbetrieb.

Jetzt starten — ab €29 → Basis €29 · Pro €49 · Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht