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ückgaberechtWarum 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.
- Automatische JSON-Serialisierung: Axios konvertiert Request-Body und Response-Daten automatisch. Mit
fetch()braucht manJSON.stringify()undresponse.json()bei jedem Aufruf. - Interceptors: Das mächtigste Feature — globale Request- und Response-Handler für Auth-Token, Logging, Error-Transformation. Mit
fetch()muss man einen eigenen Wrapper schreiben. - HTTP-Fehler als Exceptions: Axios wirft bei 4xx/5xx automatisch einen Fehler.
fetch()gibt auch bei HTTP 404 einen erfolgreichen Promise zurück — man mussresponse.okselbst prüfen. - Request-Abbruch: Axios unterstützt sowohl
AbortController(modern) als auch das ältereCancelToken-Muster. - Upload-Fortschritt:
onUploadProgressundonDownloadProgresssind eingebaut. Mitfetch()braucht manReadableStream-Magie. - Node.js kompatibel: Axios läuft identisch im Browser und in Node.js-Backends.
fetch()ist in modernen Node-Versionen verfügbar, aber das Verhalten weicht in Details ab.
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:
- Response Error: Server hat geantwortet, aber mit 4xx/5xx.
error.responseist gesetzt. - Request Error: Request wurde gesendet, aber keine Antwort erhalten (Timeout, Netzwerkfehler).
error.requestist gesetzt,error.responseist undefined. - Setup Error: Fehler bei der Request-Konfiguration, bevor der Request gesendet wurde. Weder
error.responsenocherror.requestist gesetzt.
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:
- Bundle-Größe: fetch() — 0 KB (eingebaut) | Axios — ~14 KB gzipped
- Node.js-Support: fetch() ab Node 18 | Axios seit v0.x
- Interceptors: fetch() — kein eingebauter Mechanismus | Axios — eingebaut
- Auto-JSON: fetch() — manuell | Axios — automatisch
- HTTP-Fehler als Exceptions: fetch() — nein, manuell pruefen | Axios — ja
- Upload-Fortschritt: fetch() — via ReadableStream | Axios — onUploadProgress
- Request-Abbruch: fetch() — AbortController | Axios — AbortController + CancelToken
- TypeScript: fetch() — grundlegende Typen | Axios — vollständige Generics
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:
- "Erstelle src/lib/apiClient.ts mit axios.create(), baseURL aus NEXT_PUBLIC_API_URL, 10s Timeout, Auth-Header per Interceptor aus localStorage, Token-Refresh bei 401."
- "Schreib einen Retry-Interceptor fuer apiClient: max 3 Versuche, exponentielles Backoff, nur bei 503 und Timeout, nicht bei 401/403."
- "Migrier alle fetch()-Calls in src/services/ zu apiClient. Typen aus src/types/api.ts verwenden, Fehlerbehandlung mit handleAxiosError()."
- "Schreib Tests fuer src/services/productService.ts mit axios-mock-adapter. Pro Funktion: Erfolgsfall, 404, 500, Netzwerkfehler."
- "Zeig mir, wie ich Datei-Uploads mit Fortschrittsanzeige implementiere. Fortschritt als Callback, TypeScript typisiert, Timeout 2 Minuten."
Typische Fehler — und wie Claude Code sie vermeidet
Einige Muster, die in Axios-Projekten regelmäßig Probleme machen:
- Content-Type bei FormData manuell setzen: Bricht den Upload, weil der boundary-Parameter fehlt. Claude Code lässt den Header weg, wenn es FormData erkennt.
- Interceptor-Cleanup vergessen:
interceptors.request.use()gibt eine ID zurück. Ohneinterceptors.request.eject(id)häufen sich Interceptors bei Hot-Reload. Claude Code erzeugt den Cleanup-Code, wenn der Kontext es verlangt. - Infinit-Loop beim Token-Refresh: Der Refresh-Request selbst darf nicht durch den 401-Interceptor laufen. Claude Code verwendet eine separate Axios-Instanz ohne den Response-Interceptor für den Refresh-Call.
- Ungetestete Race Conditions: Mehrere parallele 401-Fehler ohne Queue-Mechanismus führen zu mehrfachen Refresh-Requests. Das Queue-Muster im Interceptor-Abschnitt ist die korrekte Lösung.
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