Abschnitt 10 · Lektion 3
Den Body lesen
Bisher kamen alle Daten aus der Adresse: aus dem Pfad über req.params, aus der Abfrage über
req.query. Beides steht in der ersten Zeile der Anfrage und ist deshalb sofort da.
Der Body ist anders. Er kommt nach den Kopfzeilen, in Stücken über die Leitung, und wenn deine Route aufgerufen wird, ist noch nichts davon angekommen.
Warum der Body nicht einfach da ist
Erinnerst du dich an die Streams aus Abschnitt 7? Genau das ist req. Ein lesbarer Stream, der
seine Stücke über data-Ereignisse ausliefert und am Ende end meldet.
import express from "express";
const app = express();
// Ein POST laesst sich nicht in die Adresszeile tippen, deshalb steht
// hier der Befehl dafuer.
app.get("/", (req, res) =>
res.type("text/plain").send(
`curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario","jahr":2026}' http://localhost:3000/vonhand\n`
)
);
// Was express.json() im Kern tut, von Hand: der Koerper kommt in Stuecken,
// jemand muss sie einsammeln und am Ende umwandeln.
app.post("/vonhand", (req, res) => {
let roh = "";
req.on("data", (stueck) => {
roh += stueck;
});
req.on("end", () => {
const daten = JSON.parse(roh);
res.json({ zeichen: roh.length, name: daten.name });
});
});
app.listen(3000); Ein POST lässt sich nicht in die Adresszeile tippen, deshalb steht der Befehl dafür im Reiter „Browser”. Kopier ihn ins Terminal:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario","jahr":2026}' http://localhost:3000/vonhand
Fünf Zeilen, und mehr macht express.json() im Kern auch nicht: sammeln, warten, umwandeln.
Diese Fassung hat nur alle Fehler, die man dabei machen kann. Sie stürzt bei ungültigem JSON ab, statt mit 400 zu antworten. Sie nimmt beliebig viel Text entgegen. Sie prüft den Content-Type nicht. Und sie steht in dieser einen Route statt an einer Stelle für alle.
Deshalb schreibt das niemand von Hand. Aber jetzt weißt du, was passiert, wenn du es nicht tust.
Die beiden eingebauten Parser
express.json() liest einen JSON-Körper, express.urlencoded() liest ein abgeschicktes
HTML-Formular. Beide meldest du als Middleware an, ganz oben, und ab da steht das Ergebnis in
req.body.
import express from "express";
const app = express();
// Der Parser gilt nur unter /mit. Unter /ohne kennt niemand den Koerper.
app.use("/mit", express.json());
app.post("/ohne", (req, res) => res.json({ typ: typeof req.body }));
app.post("/mit", (req, res) =>
res.json({ typ: typeof req.body, name: req.body?.name ?? null })
);
app.get("/", (req, res) =>
res.type("text/plain").send(
`curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario"}' http://localhost:3000/ohne\n` +
`curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario"}' http://localhost:3000/mit\n`
)
);
app.listen(3000); Beide Pfade liegen in derselben Datei, der Parser gilt nur unter /mit. Schick denselben Körper an
beide:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario"}' http://localhost:3000/ohne
curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario"}' http://localhost:3000/mit
Merk dir die erste Antwort, sie erspart dir eine halbe Stunde: Ohne den Parser ist req.body nicht
etwa ein leeres Objekt, sondern undefined.
Das ist der häufigste „warum kommt nichts an”-Moment überhaupt. Der Aufrufer schickt korrekte Daten,
der Server bekommt sie auch, aber niemand hat sie eingesammelt. Und weil undefined.name einen
Absturz gibt, sieht der Fehler auch noch aus, als läge er woanders.
Wenn req.body bei dir undefiniert ist, sind es fast immer diese zwei Gründe: Der Parser fehlt,
oder er steht unter der Route statt darüber.
Der Content-Type ist der Schalter
Jetzt der Teil, den man selten irgendwo liest, obwohl er der Grund für die meisten verlorenen Nachmittage ist.
import express from "express";
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post("/echo", (req, res) => res.json({ body: req.body ?? null }));
// Ein Formular schickt von sich aus x-www-form-urlencoded. Damit
// deckst du den zweiten Fall mit einem Klick ab.
app.get("/", (req, res) =>
res.type("html").send(
'<form method="post" action="/echo">' +
'<p><input name="name" value="Mario"></p>' +
"<p><button>Abschicken</button></p>" +
"</form>"
)
);
app.listen(3000); Beide Parser sind angemeldet. Den Formularfall kannst du rechts anklicken, das Formular schickt von
sich aus x-www-form-urlencoded. Die beiden anderen gehen über das Terminal:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Mario"}' http://localhost:3000/echo
curl -X POST -d '{"name":"Mario"}' http://localhost:3000/echo
Beim letzten greift keiner der beiden Parser. Der Körper ist gültiges JSON, er kommt vollständig an,
und req.body bleibt trotzdem leer.
Der Grund: Jeder Parser schaut sich zuerst den Content-Type der Anfrage an und lässt den Körper
liegen, wenn er nicht zuständig ist. express.json() will application/json,
express.urlencoded() will application/x-www-form-urlencoded. Steht dort nichts oder etwas
anderes, tut keiner der beiden etwas, und zwar ohne Fehlermeldung.
Das ist auch richtig so, denn ein Server weiß sonst nicht, wie er die Bytes deuten soll. Aber es
heißt für dich: Wer JSON schickt, muss es auch dazusagen. Ein Browser mit fetch tut das nicht von
allein, und curl ebenfalls nicht.
In der Challenge zu dieser Lektion wirst du genau das sehen: Eine der Prüfungen schickt gültiges JSON ohne die Kopfzeile und erwartet einen 400. Das ist kein gemeiner Sonderfall, sondern der Normalzustand, wenn jemand deine Schnittstelle zum ersten Mal benutzt.
Ein Wort zur Größe
express.json({ limit: "100kb" }) begrenzt, wie viel du entgegennimmst. Der Standardwert liegt bei
100 Kilobyte, und wer ihn hochsetzt, sollte wissen warum.
Ohne Grenze kann jemand ein Gigabyte schicken, und dein Server sammelt es brav im Arbeitsspeicher. Ein paar solche Anfragen gleichzeitig, und der Prozess ist tot, ohne dass jemand eine Lücke ausnutzen musste. Abschnitt 15 macht daraus eine eigene Lektion, hier reicht die Merkregel: Was von außen kommt, hat eine Obergrenze.
Zum Mitnehmen
Ohne express.json() ist req.body schlicht undefined. Und ob der Parser überhaupt greift, entscheidet allein der Content-Type der Anfrage.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.