Abschnitt 16 · Lektion 5
Daten schicken
Bisher hast du nur gefragt. Jetzt geht es um die andere Richtung.
fetch nimmt ein zweites Argument, ein Objekt mit Einstellungen. Drei Felder davon brauchst du ständig: method, headers und body.
Der Körper ist immer Text
<p>Die Anfrage wird gebaut und angesehen, aber nicht abgeschickt.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} const anmeldung = {
name: "Mario",
kurs: "javascript",
newsletter: true,
};
const optionen = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(anmeldung),
};
console.log("Methode:", optionen.method);
console.log("Kopfzeile:", optionen.headers["Content-Type"]);
console.log("Typ des Körpers:", typeof optionen.body);
console.log("Körper:", optionen.body);
// So sähe der Aufruf aus. Es gibt hier keinen Endpoint,
// der ihn annimmt, deshalb bleibt die Zeile stehen.
// const antwort = await fetch("/api/anmeldung", optionen); method: "POST" sagt, dass du etwas hinschickst statt etwas zu holen. body ist das, was hingeschickt wird.
Und hier steht die Regel, die alles Weitere erklärt: Über die Leitung geht Text. Ein JavaScript-Objekt kannst du nicht abschicken, es existiert nur in deinem Browser. Deshalb steht vor jedem body ein JSON.stringify.
Wer das vergisst und das Objekt direkt übergibt, bekommt keinen Fehler, sondern das Ergebnis der stillen Umwandlung in Text: die Zeichenfolge [object Object]. Der Server sieht dann eine Anfrage, in der nichts steht, was er brauchen kann.
headers sagt dem Server, wie er den Körper lesen soll. Bei JSON ist das Content-Type: application/json. Ohne diese Zeile kann der Server nur raten, und die meisten raten dann falsch und weisen die Anfrage ab.
Merk dir die beiden als Paar: JSON.stringify im body, application/json im Header. Eines ohne das andere ergibt keinen Sinn.
Abgeschickt wird in dieser Lektion nichts, und im Reiter Netzwerk bleibt es deshalb still. In den ersten beiden Beispielen steht das fetch als Kommentar da, im dritten ist der Server durch eine Funktion ersetzt, die nach einer halben Sekunde antwortet. Der Grund ist derselbe wie in Lektion 13.5: Ein Ziel, an das man wirklich schickt, müsste die Anfrage prüfen, begrenzen und wieder aufräumen, und das ist kein Übungsstoff. Wie eine echte Anfrage in der Liste aussieht, hast du in 16.2 und 16.3 gesehen.
Die Alternative mit FormData
<form id="formular">
<label>Name <input name="name" value="Mario"></label>
<label>Kurs <input name="kurs" value="javascript"></label>
<label>Newsletter <input name="newsletter" type="checkbox" checked></label>
</form> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
button,
input,
select,
textarea {
font: inherit;
}
input,
select,
textarea {
padding: 0.35rem 0.5rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
} const formular = document.getElementById("formular");
const daten = new FormData(formular);
for (const [name, wert] of daten.entries()) {
console.log(name, "=", wert);
}
const optionen = {
method: "POST",
// Kein Content-Type. Der Browser setzt ihn selbst, samt
// Trennzeichen, das er sich ausdenkt.
body: daten,
};
console.log("Methode:", optionen.method);
console.log("Eigene Kopfzeilen:", Object.keys(optionen.headers || {}).length);
console.log("Körper ist FormData:", optionen.body instanceof FormData); FormData kennst du aus Lektion 13.4. Ein FormData-Objekt darf direkt als body stehen, ganz ohne Umwandlung.
Dabei gilt die umgekehrte Regel, und sie ist der häufigste Fehler in diesem Thema: Bei FormData lässt du den Content-Type weg.
Der Grund ist ein technischer. Der Browser packt die Felder in ein Format mit einem Trennzeichen zwischen den Teilen, und dieses Trennzeichen denkt er sich in dem Moment aus, in dem er die Anfrage baut. Es muss mit im Header stehen, damit der Server weiß, wonach er trennen soll. Setzt du den Header selbst, überschreibst du das erfundene Trennzeichen mit nichts, und der Server findet die Grenzen zwischen den Feldern nicht mehr.
Wann nimmt man was? FormData, wenn Dateien dabei sind oder die Daten ohnehin aus einem Formular kommen. JSON für alles andere, besonders wenn die Daten verschachtelt sind.
CORS in vier Sätzen
Ein Server entscheidet selbst, wessen Seiten seine Antworten sehen dürfen.
Fragt deine Seite bei einem anderen Server nach, schickt der Browser mit, von wo die Anfrage kommt. Der Server antwortet mit einer Kopfzeile, in der steht, für welche Herkunft die Antwort freigegeben ist. Passt sie nicht, hat der Browser die Antwort zwar bekommen, gibt sie deinem Code aber nicht heraus, und du siehst genau denselben Fehler wie bei einer toten Leitung.
Bei einem POST mit JSON an einen fremden Server kommt ein zusätzlicher Schritt dazu. Der Browser fragt vorher an, ob die Anfrage überhaupt erlaubt ist, mit der Methode OPTIONS. In einer Netzwerkliste stehen deshalb zwei Zeilen statt einer. Das nennt sich Preflight, und wenn dein POST scheitert, obwohl er richtig aussieht, sieh dir zuerst diese erste Zeile an.
Hier im Kurs bekommst du das nicht zu sehen: Es gibt keinen fremden Server, an den du schicken könntest, und ohne fremd kein Preflight. Diese eine Stelle musst du dir also merken, statt sie nachzusehen, und du wirst sie beim ersten eigenen POST an eine fremde Adresse wiedererkennen.
Der vollständige Handler
<form id="formular">
<label>Name <input id="name" name="name" value="Mario"></label>
<button id="senden" type="submit">Absenden</button>
</form>
<p id="status"></p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
padding: 0.4rem 0.8rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
background: #fafaf9;
cursor: pointer;
}
input,
select,
textarea {
padding: 0.35rem 0.5rem;
border: 1px solid #d6d3d1;
border-radius: 6px;
} const formular = document.getElementById("formular");
const knopf = document.getElementById("senden");
const statusFeld = document.getElementById("status");
// NACHGESTELLT: hier stünde in einer echten Anwendung das
// fetch mit method POST. Es antwortet nach kurzer Zeit und
// lehnt einen leeren Namen ab.
function schicken(daten) {
return new Promise((fertig, ablehnen) => {
setTimeout(() => {
if (!daten.get("name")) {
ablehnen(new Error("Server meldet 400"));
return;
}
fertig({ id: 17 });
}, 500);
});
}
formular.addEventListener("submit", async (ereignis) => {
ereignis.preventDefault();
statusFeld.textContent = "Wird gesendet ...";
knopf.disabled = true;
try {
const ergebnis = await schicken(new FormData(formular));
statusFeld.textContent = `Gespeichert unter der Nummer ${ergebnis.id}.`;
formular.reset();
} catch (fehler) {
statusFeld.textContent = "Konnte nicht gesendet werden. Bitte versuch es noch einmal.";
console.warn("Absenden fehlgeschlagen:", fehler.message);
} finally {
knopf.disabled = false;
}
}); Das dritte Beispiel setzt zusammen, was du in diesem Abschnitt gelernt hast: preventDefault aus 12.4, FormData aus 13.4, die vier Zustände aus 16.4 und finally aus 14.2.
Beachte, was vor dem try steht und was nach dem Absenden passiert: Das Formular wird erst zurückgesetzt, wenn wirklich gespeichert wurde. Nach einem Fehlschlag bleiben die Eingaben stehen, denn sonst darf jemand alles noch einmal tippen.
Das Absenden selbst ist hier nachgestellt, und das steht auch so im Code. Alles darum herum ist echt.
Warum diese Lektion keine Aufgabe hat
Um einen POST wirklich auszuführen, bräuchte dieser Kurs einen Endpoint, der Daten annimmt. Und der bräuchte alles, was dazugehört: eine Begrenzung der Anfragen pro Minute, eine Prüfung der Größe, einen Schutz gegen automatisierte Zugriffe und eine Antwort auf die Frage, was mit den Daten passiert.
Das ist ein eigenes Vorhaben und kein Kursinhalt. Der Stoff bleibt trotzdem hier, denn ohne ihn kennst du nur die Hälfte von fetch.
Der Satz, den du dir mitnimmst
Was der Browser schickt, kann jeder verändern. Jede Anfrage lässt sich nachbauen und mit anderen Werten wiederholen, und das ist keine Fähigkeit, für die man Werkzeuge braucht.
Du hast es hier schon gesehen. Klapp im Reiter Netzwerk eine der Anfragen aus 16.2 auf: Ganz unten steht ihre curl-Zeile. Die kopierst du in ein Terminal, änderst darin, was du willst, und schickst sie los. Der Server bekommt sie und kann nicht erkennen, dass sie nicht aus deinem Browser kam. Genauso wenig kann er erkennen, ob dein JavaScript vorher irgendetwas geprüft hat.
Deshalb ist keine Prüfung in deinem JavaScript eine Sicherheitsmaßnahme. Sie ist eine Freundlichkeit gegenüber dem Nutzer, damit er nicht auf eine Antwort warten muss, die absehbar ein Fehler wird. Die Prüfung, die zählt, passiert auf dem Server, und zwar noch einmal von vorn.
Zum Mitnehmen
Diese Lektion hat keine Aufgabe. Ein echtes Absenden bräuchte einen Server, der Daten annimmt, und der gehört nicht in einen Kurs.
Jetzt du
Basis Konto, kostenlosIm Editor änderst du die Beispiele dieser Lektion und lässt sie gleich laufen. So merkst du am schnellsten, ob es sitzt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.