Mit einem HTTP-Endpunkt zu kommunizieren bedeutete früher, zu einem Drittanbieter-Paket zu greifen. Auf dem Server gilt das nicht mehr. Seit Node.js 18 wird die globale Funktion fetch mit der Laufzeitumgebung ausgeliefert, sodass Sie HTTP-Anfragen in Node.js mit der Fetch API stellen können, und zwar über genau dieselbe Promise-basierte Schnittstelle, die Sie bereits aus dem Browser kennen, ohne node-fetch-Installation und ohne ein Flag umlegen zu müssen.
Dieser Leitfaden ist eine praxisnahe Tour durch fetch auf dem Server: GET und POST, Query-Strings, das Senden und Lesen von JSON, die Regel zur Fehlerbehandlung, über die fast jeder stolpert, Timeouts mit AbortController, das nebenläufige Ausführen von Anfragen und die Frage, wo Axios noch immer seine Berechtigung hat. Den Abschluss bildet das eine Szenario, in dem eine reine Anfrage nicht ausreicht, das Scraping einer echten Website, und wie Sie gerenderte HTML stattdessen über eine API abrufen.
Warum fetch, und was sich in Node geändert hat
Die Fetch API ist Promise-basiert und lässt sich daher sauber mit async/await kombinieren, ohne die Callback-Verschachtelung des alten http-Moduls. Sie beherrscht jede Methode, die Sie brauchen (GET, POST, PUT, PATCH, DELETE), liefert Ihnen ein echtes Headers-Objekt und verarbeitet JSON mit einem einzigen Methodenaufruf. Die Schnittstelle ist in Browser und Server identisch, was bedeutet, dass Code und mentale Modelle in beide Richtungen übertragbar sind.
Die praktische Schlagzeile ist die Versionsgrenze. Ab Node.js 18 und später ist fetch global, genau wie setTimeout. Sie importieren es nicht und Sie installieren nichts. Prüfen Sie Ihre Laufzeitumgebung, bevor Sie Code schreiben:
node -v # v18.x or higher means global fetch is available
Nur unter Node 16 oder älter, wo Sie node-fetch installieren und importieren würden. Unter Node 18+ ist dieses Paket überflüssig, und es hinzuzufügen überdeckt lediglich die schnellere eingebaute Variante. Wenn Sie auf einem aktuellen LTS-Release sind, löschen Sie die Abhängigkeit und nutzen Sie die globale Funktion.
GET-Anfragen mit der Fetch API stellen
Eine GET-Anfrage ist ein einzelner Aufruf. fetch() gibt ein Promise zurück, das zu einem Response-Objekt aufgelöst wird, sobald die Header eintreffen. Der Body wird separat gelesen, mit response.json() für JSON oder response.text() für reinen Text, und jede dieser Methoden gibt ihr eigenes Promise zurück.
Das wichtigste Detail: prüfen Sie immer response.ok, bevor Sie den Body lesen. Ein 404 oder 500 ist aus Sicht von fetch nach wie vor ein erfolgreicher HTTP-Austausch, daher wird das Promise ganz normal aufgelöst. response.ok ist nur für Statuscodes im Bereich von 200 bis 299 true.
async function getPost(id) { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`); if (!response.ok) { throw new Error(`HTTP ${response.status} ${response.statusText}`); } const data = await response.json(); return data; } getPost(1).then(console.log).catch(console.error);
Wenn der Endpunkt Text oder HTML statt JSON zurückgibt, tauschen Sie den Body-Reader aus. Der Rest des Ablaufs bleibt unverändert.
const response = await fetch("https://example.com/"); const html = await response.text(); console.log(html.slice(0, 200));
Sie können den Body genau einmal lesen. response.json() nach response.text() auf derselben Response aufzurufen wirft einen Fehler, weil der Stream bereits verbraucht ist. Wählen Sie einen Reader pro Response.
Response-Header lesen und setzen
Das Response-Objekt stellt eine headers-Eigenschaft bereit, die sich wie eine Map verhält. Lesen Sie einen einzelnen Header mit get() oder iterieren Sie über die gesamte Menge. Header-Namen sind case-insensitiv, sodass "content-type" und "Content-Type" zum selben Wert aufgelöst werden.
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1"); console.log(response.headers.get("content-type")); for (const [key, value] of response.headers) { console.log(`${key}: ${value}`); }
Ein Vorteil der Ausführung auf dem Server: die CORS-Beschränkungen, die im Browser die meisten Header verbergen, gelten in Node nicht. Sie erhalten vollen Zugriff auf jeden Response-Header, den der Server sendet.
Query-Strings mit URLSearchParams hinzufügen
Sie könnten Query-Parameter von Hand zusammensetzen, aber das bedeutet, Leerzeichen, Ampersands und andere reservierte Zeichen selbst zu maskieren, und dabei ist leicht ein Fehler unterlaufen. URLSearchParams kodiert Werte korrekt und liest sich sauber.
const params = new URLSearchParams({ userId: 1, _limit: 5, }); const url = `https://jsonplaceholder.typicode.com/posts?${params}`; const response = await fetch(url); const posts = await response.json(); console.log(`Got ${posts.length} posts`);
Das Interpolieren von params in ein Template-Literal ruft dessen toString() auf, was einen korrekt kodierten Query-String erzeugt. Einen Filter später hinzuzufügen oder zu entfernen ist eine einzeilige Änderung am Objekt, keine String-Operation.
POST-Anfragen und JSON-Bodies senden
Um Daten zu senden, übergeben Sie ein zweites Argument: ein Optionsobjekt mit method, headers und body. Der Body muss ein String sein, daher laufen JSON-Payloads durch JSON.stringify(), und Sie setzen Content-Type: application/json, damit der Server weiß, wie er das Empfangene parsen soll.
async function createPost(payload) { const response = await fetch("https://jsonplaceholder.typicode.com/posts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`POST failed: HTTP ${response.status}`); } return response.json(); } const created = await createPost({ title: "Fetch on the server", body: "Built into Node 18+", userId: 1, }); console.log(created);
Dieselbe Struktur deckt PUT und PATCH für Aktualisierungen und DELETE für Löschungen ab: ändern Sie method und, wo relevant, den body. Für Formularübermittlungen statt JSON übergeben Sie eine URLSearchParams- oder FormData-Instanz als Body und lassen den expliziten Content-Type weg; fetch setzt den korrekten Header dafür automatisch.
Fehlerbehandlung: die Regel, die jeden erwischt
Hier ist das Wichtigste, das Sie sich über fetch verinnerlichen müssen. Es lehnt nur bei Fehlern auf Netzwerkebene ab: die DNS-Auflösung ist fehlgeschlagen, die Verbindung wurde getrennt, die Anfrage wurde abgebrochen. Jede HTTP-Antwort vom Server, einschließlich 404 und 500, gilt als aufgelöstes Promise. Ein reines try/catch um fetch schluckt bereitwillig einen 500 und macht weiter, als wäre alles in Ordnung.
Das robuste Muster kombiniert beide Prüfungen: ein try/catch für die Netzwerkschicht und einen expliziten response.ok-Test für die HTTP-Schicht.
async function getJson(url) { try { const response = await fetch(url); if (!response.ok) { // HTTP error: server replied, but with a 4xx or 5xx throw new Error(`HTTP ${response.status} for ${url}`); } return await response.json(); } catch (error) { // Network failure, abort, or the thrown HTTP error above console.error("Request failed:", error.message); throw error; } }
Bei !response.ok einen Fehler zu werfen leitet HTTP-Fehler in dasselbe catch wie Netzwerkfehler, sodass ein Block beide Fehlerklassen behandelt. Ohne diese explizite Prüfung rutscht ein 500 direkt zu response.json() durch, das dann statt des echten Status einen verwirrenden Parse-Fehler auf der HTML-Fehlerseite wirft.
Timeouts und Abbruch mit AbortController
fetch hat keinen eingebauten Timeout. Sich selbst überlassen kann eine Anfrage so lange hängen, wie die Verbindung offen bleibt, was für alles Benutzerseitige inakzeptabel ist. Die Standardlösung ist AbortController: erstellen Sie einen, übergeben Sie sein signal an fetch und rufen Sie abort() auf, wenn ein Timer ausgelöst wird.
async function fetchWithTimeout(url, ms = 5000) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), ms); try { const response = await fetch(url, { signal: controller.signal }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { if (error.name === "AbortError") { throw new Error(`Request to ${url} timed out after ${ms}ms`); } throw error; } finally { clearTimeout(timer); } }
Eine abgebrochene Anfrage lehnt mit einem AbortError ab, weshalb das catch error.name inspiziert, um eine klare Timeout-Meldung zu liefern. Den Timer im finally zu löschen verhindert, dass eine erfolgreiche frühe Antwort später einen verirrten Abbruch auslöst. Dasselbe signal kann fetch auch mit einem benutzergesteuerten Abbrechen-Button verbinden, nicht nur mit einem Timer. In neueren Node-Versionen ist AbortSignal.timeout(ms) eine Kurzform für genau dieses Timer-Muster.
Anfragen nebenläufig ausführen mit Promise.all
Wenn Sie mehrere unabhängige Anfragen haben, verschwendet das Abwarten nacheinander Zeit: jede wartet darauf, dass die vorherige abgeschlossen ist. Promise.all stößt sie alle auf einmal an und löst auf, wenn die letzte eintrifft, sodass die Gesamtzeit ungefähr der langsamsten Anfrage entspricht statt der Summe aller.
const ids = [1, 2, 3, 4, 5]; const posts = await Promise.all( ids.map((id) => getPost(id).catch((error) => ({ id, error: error.message })), ), ); console.log(posts);
Ein Vorbehalt: Promise.all lehnt ab, sobald ein einzelnes Promise ablehnt, und verwirft den Rest. Das oben eingebettete .catch() verwandelt einen Fehler pro Anfrage in einen Wert, sodass eine fehlerhafte Anfrage nicht den gesamten Batch versenkt. Wenn Sie lieber jedes Ergebnis explizit prüfen möchten, gibt Promise.allSettled einen Status-und-Wert-Datensatz für jedes zurück. Und feuern Sie nicht Tausende von Anfragen auf einmal ab: so werden Sie rate-limitiert oder gehen die Sockets aus. Für große Aufträge begrenzen Sie die Nebenläufigkeit, indem Sie die Liste in Blöcke fester Größe verarbeiten.
Fetch API vs Axios: wozu greifen
Axios ist älter als serverseitiges fetch und hat noch immer eine treue Anhängerschaft. Der ehrliche Vergleich ist kurz:
-
Greifen Sie zu
fetch, wenn Sie null Abhängigkeiten und eine Standard-API wollen. Es ist eingebaut, es ist im Browser dasselbe, und für die meiste GET- und POST-Arbeit ist es alles, was Sie brauchen. - Greifen Sie zu Axios, wenn Sie die Annehmlichkeiten wollen, die es mitbringt: automatisches JSON-Parsing in beide Richtungen, Request- und Response-Interceptoren, eingebaute Timeout-Konfiguration, automatische Ablehnung bei Nicht-2xx-Status sowie Upload- oder Download-Fortschritt. In einer großen Codebasis ersparen diese Features echten Boilerplate.
Der Abstand ist kleiner, als er einmal war. Die meisten Axios-Annehmlichkeiten, Timeouts, Status-Prüfungen, JSON-Verarbeitung, sind ein paar Zeilen Hilfscode über fetch, wie die Wrapper in diesem Artikel. Wenn Sie auf Node 18+ frisch starten und keine Interceptoren brauchen, ist die eingebaute Variante die schlankere Wahl. Wenn ein Projekt bereits auf Axios standardisiert ist, gibt es keine Dringlichkeit, es herauszureißen.
Wo reines fetch an seine Grenzen stößt: echte Sites scrapen
Alles oben funktioniert wunderbar gegen APIs und kooperative Server. Richten Sie fetch jedoch auf eine moderne kommerzielle Website, um deren HTML abzurufen, dann stoßen Sie schnell auf zwei harte Grenzen.
Erstens ruft fetch nur das anfängliche HTML ab, das der Server sendet. Es führt keinen Browser aus, also führt es niemals das JavaScript der Seite aus. Auf einer Site, die ihren Inhalt clientseitig rendert, ist das Markup, das Sie zurückbekommen, eine nahezu leere Hülle, in der die gewünschten Daten erst später von Skripten befüllt werden, die nie laufen. Zweitens nehmen Sites, denen Scraping etwas bedeutet, einen Fingerabdruck des eingehenden Traffics. Eine Anfrage von einer Rechenzentrums-IP mit einem Standard-Node-User-Agent und ohne browserähnliche Header wird herausgefordert oder blockiert, bevor sie etwas Nützliches zurückgibt.
Sie können beides selbst angehen, mit einem Headless-Browser zum Rendern der Seite und einem Pool rotierender Residential-Proxys, um wie echte Besucher auszusehen, aber diesen Stack gesund zu halten ist der Großteil der Arbeit. Der sauberere Weg ist, Ihren fetch-Aufruf an eine API zu senden, die das Rendern und die IP-Rotation serverseitig erledigt und Ihnen fertiges HTML übergibt.
Eine echte Site zu scrapen braucht eine gerenderte Seite hinter einer vertrauenswürdigen IP, in einer Anfrage. Die Crawling API nimmt Ihren Token und eine Ziel-URL, führt die Seite in einem echten Browser aus, rotiert Residential-IPs für Sie und gibt das fertige HTML zurück, sodass Sie weiterhin schlichtes fetch nutzen und sich den Betrieb einer Headless-Flotte und eines Proxy-Pools sparen. Starten Sie auf dem kostenlosen Tarif.
Der Aufruf ist dasselbe fetch, das Sie bereits kennen. Sie bauen die Endpunkt-URL mit Ihrem Token und dem URL-kodierten Ziel, senden ein GET und lesen das gerenderte HTML aus der Response. Von dort aus verwandelt ein Parser wie cheerio dieses HTML in strukturierte Daten.
import * as cheerio from "cheerio"; const TOKEN = "YOUR_CRAWLBASE_TOKEN"; async function scrape(targetUrl) { const params = new URLSearchParams({ token: TOKEN, url: targetUrl, }); const response = await fetch(`https://api.crawlbase.com/?${params}`); if (!response.ok) { throw new Error(`Crawl failed: HTTP ${response.status}`); } const html = await response.text(); const $ = cheerio.load(html); return { title: $("title").text().trim(), headings: $("h2").map((_, el) => $(el).text().trim()).get(), }; } scrape("https://www.example.com/").then(console.log);
Für Seiten, die Inhalt mit JavaScript rendern, fügen Sie ein "&javascript=true"-Äquivalent hinzu, indem Sie einen JS-Rendering-Token übergeben, der das Ziel in einem echten Browser ausführt, bevor er HTML zurückgibt. Da die Anfrage nur fetch ist, gilt jede Technik aus diesem Artikel weiterhin: packen Sie sie in den Timeout-Helper, lassen Sie eine Liste von URLs über Promise.all mit einer sinnvollen Nebenläufigkeitsgrenze laufen und prüfen Sie response.ok bei jedem Aufruf. Für eine ausführlichere Node-Scraping-Anleitung siehe wie man einen Web-Scraper mit Node.js baut, und für das Rendering-Problem im Speziellen wie man JavaScript-Websites crawlt.
Wichtigste Erkenntnisse
-
fetch ist in Node 18+ eingebaut. Kein
node-fetch, kein Flag, kein Import. Es ist global, genauso wie im Browser. -
Prüfen Sie immer
response.ok.fetchlehnt nur bei Netzwerkfehlern ab, sodass ein 404 oder 500 normal aufgelöst wird und an einem schlichtentry/catchvorbeischlüpft. -
POST ist ein zweites Argument. Setzen Sie
method, einenContent-Type-Header und einenJSON.stringify'tenbody; bauen Sie Query-Strings mitURLSearchParams. -
Fügen Sie Timeouts mit
AbortControllerhinzu. Es gibt keinen eingebauten Timeout; übergeben Sie einsignalund brechen Sie bei einem Timer oder einer Abbruch-Aktion ab. -
Parallelisieren Sie mit
Promise.all. Feuern Sie unabhängige Anfragen gemeinsam ab, sichern Sie jede mit.catch()ab und begrenzen Sie die Nebenläufigkeit für große Batches. -
Reines fetch kann moderne Sites nicht scrapen. Es rendert kein JavaScript und wird blockiert; leiten Sie dasselbe
fetchdurch die Crawling API für gerendertes HTML und parsen Sie dann mit cheerio.
Häufig gestellte Fragen
Brauche ich node-fetch in Node.js noch?
Nicht auf Node.js 18 oder später. fetch ist dort global, sodass Sie es ohne Installation und ohne Import direkt aufrufen können. node-fetch brauchen Sie nur auf Node 16 oder älter. Wenn Sie auf einem aktuellen LTS-Release sind, ist das Entfernen der Abhängigkeit sicher und ermöglicht Ihnen, die schnellere eingebaute Implementierung zu nutzen.
Warum wirft fetch bei einem 404 oder 500 keinen Fehler?
Weil fetch jeden abgeschlossenen HTTP-Austausch als Erfolg behandelt, selbst wenn der Statuscode ein Fehler ist. Das Promise lehnt nur bei Fehlern auf Netzwerkebene wie einer getrennten Verbindung, einem DNS-Fehler oder einem Abbruch ab. Um HTTP-Fehler abzufangen, müssen Sie selbst response.ok prüfen (nur für 200 bis 299 true) und einen Fehler werfen, wenn es false ist, und ihn dann von Ihrem catch behandeln lassen.
Wie sende ich JSON in einer POST-Anfrage mit fetch?
Übergeben Sie als zweites Argument ein Optionsobjekt mit method: "POST", einem headers-Objekt, das "Content-Type": "application/json" setzt, und einem body, der von JSON.stringify() erzeugt wird. Der Body muss ein String sein, weshalb Sie das Objekt zuerst stringifizieren. Lesen Sie die Antwort des Servers mit await response.json() zurück, nachdem Sie response.ok bestätigt haben.
Wie füge ich einer fetch-Anfrage einen Timeout hinzu?
Verwenden Sie einen AbortController. Erstellen Sie einen, übergeben Sie sein signal an die fetch-Optionen und rufen Sie controller.abort() aus einem setTimeout auf. Eine abgebrochene Anfrage lehnt mit einem AbortError ab, prüfen Sie also error.name in Ihrem catch, um einen Timeout sauber zu melden. In neueren Node-Versionen verpackt AbortSignal.timeout(ms) dieses Muster in einen einzigen Aufruf.
Soll ich in Node fetch oder Axios verwenden?
Verwenden Sie fetch für abhängigkeitsfreie, standardbasierte Anfragen; es deckt die meiste GET- und POST-Arbeit auf Node 18+ von Haus aus ab. Wählen Sie Axios, wenn Sie dessen eingebaute Annehmlichkeiten wollen, etwa Interceptoren, automatische JSON-Verarbeitung, konfigurierte Timeouts und automatische Ablehnung bei Nicht-2xx-Antworten. Für ein frisches Projekt, das diese nicht braucht, ist die eingebaute Variante die schlankere Wahl; eine bestehende Axios-Codebasis hat keinen dringenden Grund zu wechseln.
Kann ich fetch zum Scrapen einer Website verwenden?
Sie können das rohe HTML einer Seite abrufen, aber reines fetch hat auf echten Sites zwei Grenzen: es führt kein JavaScript aus, sodass clientseitig gerenderter Inhalt leer zurückkommt, und Anfragen von Rechenzentrums-IPs werden herausgefordert oder blockiert. Die Lösung ist, Ihren fetch-Aufruf an die Crawling API zu senden, die die Seite in einem echten Browser hinter rotierenden Residential-IPs rendert und fertiges HTML zurückgibt, das Sie mit cheerio parsen können.
Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.
Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.
