Zum Inhalt springen
aviral gupta

// I6.1 · ca. 38 Min. · Aufbau

fetch und JSON: Daten laden und senden

Nach dieser Lektion laden Sie JSON mit fetch von einem Server, unterscheiden eine gescheiterte Anfrage von einem Fehlerstatus und senden JSON mit einer POST-Anfrage.

Lektion 1 von 5 in I6 Netzwerkanfragen und Speicher im Client

Anfang des Moduls

Danach können Sie

  • JSON mit fetch laden: status, ok und headers der Response lesen, dann den Body mit json() oder text()
  • Sagen, wann fetch abgelehnt wird (gar keine Antwort) und wann es mit einem Fehlerstatus wie 404 erfüllt wird
  • JSON per POST senden, mit Content-Type-Header und JSON.stringify, und jeden Body nur einmal lesen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Lektion I1.3. Was gibt das aus?

    async function load() {
      return 42;
    }
    const result = load();
    console.log(result instanceof Promise, await result);
  2. Vorhersagen · Aufgabe 2 von 7

    Raten Sie, bevor Sie weiterlesen. api ist die Adresse eines kleinen Testservers, der keine Seite /nope hat. Was gibt das aus?

    try {
      const response = await fetch(api + "/nope");
      console.log("fulfilled", response.status, response.ok);
    } catch (error) {
      console.log("rejected", error.name);
    }
  3. Üben · Aufgabe 3 von 7

    Der Server schickt ein JSON-Array mit Büchern. Ergänzen Sie die Methode, die den Body liest und als JSON parst.

    const books = await response.____();
    const books = await response.();
  4. Üben · Aufgabe 4 von 7

    Der Server schickt /books mit dem Header Content-Type: application/json. Was gibt das aus?

    const response = await fetch(api + "/books");
    console.log(response.status, response.headers.get("CONTENT-TYPE"));
  5. Üben · Aufgabe 5 von 7

    /echo des Testservers schickt genau den Body zurück, den er bekommen hat. Was gibt das aus?

    const response = await fetch(api + "/echo", {
      method: "POST",
      body: {title: "Dune"}
    });
    console.log(await response.text());
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Der Server schickt zwei Bücher. Was gibt das aus?

    const response = await fetch(api + "/books");
    console.log((await response.json()).length);
    console.log((await response.json()).length);
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: Kopieren Sie server.js aus dem durchgespielten Beispiel. Schreiben Sie addBook(api, book), die das Buch als JSON per POST an /books schickt und das Buch zurückgibt, das der Server zurücksendet (mit seiner neuen id). Fügen Sie {title: "Beloved", year: 1987} hinzu, geben Sie id und title aus, laden Sie dann /books und geben Sie aus, wie viele Bücher es gibt. Führen Sie node main.js aus.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Bücher laden, vermissen und senden

server.js ist ein kleiner Testserver aus der Standardbibliothek von Node.js: Er lauscht auf einem freien Port Ihres eigenen Computers und kennt /books, /books/1 und /books/2. main.js startet ihn, lädt die Liste, fragt nach einem Buch, das es nicht gibt, sendet per POST ein neues und stoppt zum Schluss den Server und versucht es noch einmal.

main.js

import {startServer} from "./server.js";

const server = await startServer();
const api = server.url; // http://127.0.0.1 and a free port

const response = await fetch(api + "/books");
console.log(response.status, response.ok, response.headers.get("content-type"));
const books = await response.json(); // the body, parsed
console.log(books.map((book) => book.title));

const missing = await fetch(api + "/books/9"); // fulfils: a 404 is a response too
console.log(missing.status, missing.ok, await missing.text());

const created = await fetch(api + "/books", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({title: "Kindred", year: 1979})
});
console.log(created.status, await created.json());

await server.close();
try {
  await fetch(api + "/books"); // nobody answers any more
} catch (error) {
  console.log(error.name + ": " + error.message);
}

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const books = [
    {id: 1, title: "Dune", year: 1965},
    {id: 2, title: "Emma", year: 1815}
  ];
  const server = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const id = Number(request.url.split("/")[2]); // "/books/2" gives 2
    if (request.method === "GET" && request.url === "/books") {
      send(200, "application/json", JSON.stringify(books));
    } else if (request.method === "GET" && request.url.startsWith("/books/")) {
      const book = books.find((b) => b.id === id);
      if (book) send(200, "application/json", JSON.stringify(book));
      else send(404, "text/plain", "Not found");
    } else if (request.method === "POST" && request.url === "/books") {
      if (request.headers["content-type"] !== "application/json") {
        return send(415, "application/json", JSON.stringify({error: "Send JSON"}));
      }
      try {
        const book = {id: books.length + 1, ...JSON.parse(body)};
        books.push(book);
        send(201, "application/json", JSON.stringify(book));
      } catch {
        send(400, "application/json", JSON.stringify({error: "Bad JSON"}));
      }
    } else if (request.method === "POST" && request.url === "/echo") {
      send(200, "text/plain", body); // sends back exactly what it got
    } else {
      send(404, "text/plain", "Not found");
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

Ausführen mit

node main.js

Ausgabe

200 true application/json
[ 'Dune', 'Emma' ]
404 false Not found
201 { id: 3, title: 'Kindred', year: 1979 }
TypeError: fetch failed
  • Zwei awaits pro Response: eines für fetch, eines für den Body.
  • Das fehlende Buch hat nichts geworfen: fetch wurde mit dem Status 404 erfüllt, und ok war false.
  • Erst als gar nichts mehr antwortete, wurde fetch abgelehnt, mit TypeError: fetch failed.

Übungen

Übung 1 von 3

Die Titel vom Server

getTitles(baseUrl) soll baseUrl + "/books" laden, ein JSON-Array mit Büchern, und ein Array ihrer Titel zurückgeben. Im Starter fehlen zwei awaits, daher ruft er json() auf einem Promise statt auf einer Response auf.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: response.json is not a function. Was ist response, bevor Sie darauf warten?

  2. Hinweis 2

    fetch gibt ein Promise einer Response zurück, und json() gibt ein Promise der Daten zurück.

  3. Hinweis 3

    const response = await fetch(…); const books = await response.json();

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function getTitles(baseUrl) {
  const response = await fetch(baseUrl + "/books");
  const books = await response.json();
  return books.map((book) => book.title);
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export async function getTitles(baseUrl) {
  const response = fetch(baseUrl + "/books");
  const books = response.json();
  return books.map((book) => book.title);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {getTitles} from './main.js';

test('getTitles gives the titles of both books', async () => {
  const server = await startServer();
  try {
    const titles = await getTitles(server.url);
    assert.deepEqual(titles, ['Dune', 'Emma'], `getTitles gave ${JSON.stringify(titles)}`);
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const books = [
    {id: 1, title: "Dune", year: 1965},
    {id: 2, title: "Emma", year: 1815}
  ];
  const server = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const id = Number(request.url.split("/")[2]); // "/books/2" gives 2
    if (request.method === "GET" && request.url === "/books") {
      send(200, "application/json", JSON.stringify(books));
    } else if (request.method === "GET" && request.url.startsWith("/books/")) {
      const book = books.find((b) => b.id === id);
      if (book) send(200, "application/json", JSON.stringify(book));
      else send(404, "text/plain", "Not found");
    } else if (request.method === "POST" && request.url === "/books") {
      if (request.headers["content-type"] !== "application/json") {
        return send(415, "application/json", JSON.stringify({error: "Send JSON"}));
      }
      try {
        const book = {id: books.length + 1, ...JSON.parse(body)};
        books.push(book);
        send(201, "application/json", JSON.stringify(book));
      } catch {
        send(400, "application/json", JSON.stringify({error: "Bad JSON"}));
      }
    } else if (request.method === "POST" && request.url === "/echo") {
      send(200, "text/plain", body); // sends back exactly what it got
    } else {
      send(404, "text/plain", "Not found");
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 3

Ein Buch oder null

findBook(baseUrl, id) lädt baseUrl + "/books/" + id und gibt das Buch zurück. Für ein Buch, das es nicht gibt, antwortet der Server mit 404 und dem Text Not found, und findBook muss null zurückgeben. Der Starter parst jede Antwort als JSON, daher wirft ein fehlendes Buch einen SyntaxError. Prüfen Sie zuerst den Status.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Buch 9 gibt einen SyntaxError. Was hat der Server geschickt, und wurde fetch abgelehnt?

  2. Hinweis 2

    Ein 404 erfüllt das fetch-Promise trotzdem. response.status nennt Ihnen den Code.

  3. Hinweis 3

    if (response.status === 404) return null; bevor Sie json() aufrufen.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function findBook(baseUrl, id) {
  const response = await fetch(baseUrl + "/books/" + id);
  if (response.status === 404) return null;
  return response.json();
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export async function findBook(baseUrl, id) {
  const response = await fetch(baseUrl + "/books/" + id);
  return response.json();
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {findBook} from './main.js';

test('findBook gives book 2', async () => {
  const server = await startServer();
  try {
    const book = await findBook(server.url, 2);
    assert.deepEqual(book, {id: 2, title: 'Emma', year: 1815}, `findBook gave ${JSON.stringify(book)}`);
  } finally {
    await server.close();
  }
});

test('findBook gives null for a book that does not exist', async () => {
  const server = await startServer();
  try {
    const book = await findBook(server.url, 9);
    assert.equal(book, null, `findBook gave ${JSON.stringify(book)}`);
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const books = [
    {id: 1, title: "Dune", year: 1965},
    {id: 2, title: "Emma", year: 1815}
  ];
  const server = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const id = Number(request.url.split("/")[2]); // "/books/2" gives 2
    if (request.method === "GET" && request.url === "/books") {
      send(200, "application/json", JSON.stringify(books));
    } else if (request.method === "GET" && request.url.startsWith("/books/")) {
      const book = books.find((b) => b.id === id);
      if (book) send(200, "application/json", JSON.stringify(book));
      else send(404, "text/plain", "Not found");
    } else if (request.method === "POST" && request.url === "/books") {
      if (request.headers["content-type"] !== "application/json") {
        return send(415, "application/json", JSON.stringify({error: "Send JSON"}));
      }
      try {
        const book = {id: books.length + 1, ...JSON.parse(body)};
        books.push(book);
        send(201, "application/json", JSON.stringify(book));
      } catch {
        send(400, "application/json", JSON.stringify({error: "Bad JSON"}));
      }
    } else if (request.method === "POST" && request.url === "/echo") {
      send(200, "text/plain", body); // sends back exactly what it got
    } else {
      send(404, "text/plain", "Not found");
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 3 von 3

Ein neues Buch senden

addBook(baseUrl, book) soll das Buch als JSON per POST an baseUrl + "/books" schicken und zurückgeben, was der Server zurücksendet: das Buch mit seiner neuen id. Der Server nimmt nur einen Body mit dem Header Content-Type: application/json an. Der Starter sendet das Objekt selbst, ohne den Header, und bekommt ein Fehlerobjekt zurück.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: addBook gibt {"error":"Send JSON"} zurück. Was will der Server sehen?

  2. Hinweis 2

    Zwei Dinge fehlen: eine headers-Option und ein Body, der ein JSON-String ist.

  3. Hinweis 3

    headers: {"Content-Type": "application/json"}, body: JSON.stringify(book)

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export async function addBook(baseUrl, book) {
  const response = await fetch(baseUrl + "/books", {
    method: "POST",
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify(book)
  });
  return response.json();
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export async function addBook(baseUrl, book) {
  const response = await fetch(baseUrl + "/books", {
    method: "POST",
    body: book
  });
  return response.json();
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {startServer} from './server.js';
import {addBook} from './main.js';

test('addBook gives the new book with id 3', async () => {
  const server = await startServer();
  try {
    const added = await addBook(server.url, {title: 'Kindred', year: 1979});
    assert.deepEqual(added, {id: 3, title: 'Kindred', year: 1979}, `addBook gave ${JSON.stringify(added)}`);
  } finally {
    await server.close();
  }
});

test('After addBook the server has three books', async () => {
  const server = await startServer();
  try {
    await addBook(server.url, {title: 'Kindred', year: 1979});
    const books = await (await fetch(server.url + '/books')).json();
    assert.equal(books.length, 3, `the server has ${books.length} books`);
  } finally {
    await server.close();
  }
});

server.js

// A small test server for this lesson, made with node:http. You do not need to change it.
import {createServer} from "node:http";

export async function startServer() {
  const books = [
    {id: 1, title: "Dune", year: 1965},
    {id: 2, title: "Emma", year: 1815}
  ];
  const server = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    const send = (status, type, text) => {
      response.writeHead(status, {"Content-Type": type});
      response.end(text);
    };
    const id = Number(request.url.split("/")[2]); // "/books/2" gives 2
    if (request.method === "GET" && request.url === "/books") {
      send(200, "application/json", JSON.stringify(books));
    } else if (request.method === "GET" && request.url.startsWith("/books/")) {
      const book = books.find((b) => b.id === id);
      if (book) send(200, "application/json", JSON.stringify(book));
      else send(404, "text/plain", "Not found");
    } else if (request.method === "POST" && request.url === "/books") {
      if (request.headers["content-type"] !== "application/json") {
        return send(415, "application/json", JSON.stringify({error: "Send JSON"}));
      }
      try {
        const book = {id: books.length + 1, ...JSON.parse(body)};
        books.push(book);
        send(201, "application/json", JSON.stringify(book));
      } catch {
        send(400, "application/json", JSON.stringify({error: "Bad JSON"}));
      }
    } else if (request.method === "POST" && request.url === "/echo") {
      send(200, "text/plain", body); // sends back exactly what it got
    } else {
      send(404, "text/plain", "Not found");
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); // port 0: any free port
  return {
    url: `http://127.0.0.1:${server.address().port}`,
    close: () => new Promise((resolve) => server.close(resolve))
  };
}

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Den Body zweimal lesen

import {startServer} from "./server.js";

const server = await startServer();
try {
  const response = await fetch(server.url + "/books");
  console.log("Books:", (await response.json()).length);
  const books = await response.json();
  console.log(books[0].title);
} finally {
  await server.close();
}

Was Node.js ausgibt

TypeError: Body is unusable: Body has already been read

Warum, und die Lösung

Der Body ist ein Stream, und json() liest ihn bis zum Ende. Ein zweites json(), text() oder jede andere Body-Methode hat nichts mehr zu lesen. Lesen Sie den Body einmal, behalten Sie das Ergebnis in einer Variablen und verwenden Sie diese: const books = await response.json(); console.log("Books:", books.length);.

Kein await vor fetch

import {startServer} from "./server.js";

const server = await startServer();
try {
  const response = fetch(server.url + "/books");
  const books = await response.json();
  console.log(books.length);
} finally {
  await server.close();
}

Was Node.js ausgibt

TypeError: response.json is not a function

Warum, und die Lösung

fetch gibt ein Promise zurück, nicht die Response. Ein Promise hat then und catch, aber keine Methode json. Warten Sie zuerst auf fetch: const response = await fetch(…);, und warten Sie dann auch auf den Body.

Eine Fehlerseite als JSON parsen

import {startServer} from "./server.js";

const server = await startServer();
try {
  const response = await fetch(server.url + "/books/9");
  const book = await response.json();
  console.log(book.title);
} finally {
  await server.close();
}

Was Node.js ausgibt

SyntaxError: Unexpected token 'N', "Not found" is not valid JSON

Warum, und die Lösung

Es gibt kein Buch 9, daher antwortete der Server mit 404 und dem Text Not found. fetch wurde nicht abgelehnt, denn ein 404 ist eine Antwort. json() versuchte dann, diesen Text zu parsen. Prüfen Sie zuerst die Response: if (!response.ok) { … }, bevor Sie den Body als JSON lesen.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

fetch liefert eine Response

fetch(url) schickt eine GET-Anfrage und gibt ein Promise zurück. Es wird mit einer Response erfüllt, sobald Status und Header angekommen sind. response.status ist die Zahl, etwa 200 oder 404, response.ok ist true für 200 bis 299, und response.headers.get("content-type") liest einen Header, in beliebiger Schreibweise. Der Body kann noch unterwegs sein, sein Lesen ist daher ein zweites Promise: await response.json() parst ihn als JSON, await response.text() liefert den reinen Text. In Node.js 24 ist fetch eingebaut und funktioniert wie im Browser.

Ein 404 ist keine Ablehnung

Das fetch-Promise wird nur abgelehnt, wenn gar keine Antwort ankommt: Der Server ist nicht erreichbar oder die URL ist kaputt. Dann erhalten Sie TypeError: fetch failed. Ein 404 oder 500 ist trotzdem eine Antwort, fetch wird also erfüllt, und nur response.ok verrät Ihnen, dass etwas schiefgegangen ist. Rufen Sie json() auf einer Fehlerseite auf, erhalten Sie einen SyntaxError über Text, der kein JSON ist. Prüfen Sie also response.ok oder den Status, bevor Sie den Body verwenden. Lektion I6.2 macht aus dieser Prüfung Fehler und Wiederholungsversuche.

JSON senden

Um Daten zu senden, übergeben Sie Optionen: method: "POST", headers: {"Content-Type": "application/json"} und body: JSON.stringify(data). Ohne JSON.stringify wird das Objekt zum Text "[object Object]". Ohne den Header kennzeichnet fetch einen String-Body als text/plain, und viele Server lehnen das ab. Ohne method ist die Anfrage ein GET, und der darf gar keinen Body haben. Ein Body ist ein Stream, der nur einmal gelesen werden kann: Ein zweites json() wirft TypeError: Body is unusable. Behalten Sie den geparsten Wert stattdessen in einer Variablen.

Quellen

Zuletzt geprüft am 5. Oktober 2026