mitmario.dev

Den Body lesen

Node.js Sandbox 3 Min Lesezeit 3 BeispieleLektion 3 von 7

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.

Den Körper von Hand einsammeln
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.

Mit und ohne den Parser
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.

Der Content-Type entscheidet
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, kostenlos

Zu 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.