Abschnitt 13 · Lektion 1
Formulardaten annehmen
In Abschnitt 11 und 12 war dein Gegenüber immer ein Programm. Es schickte JSON, weil du es so verlangt hast. Ab hier sitzt ein Mensch davor, klickt auf einen Knopf in einer Webseite, und was dann bei dir ankommt, hat sich niemand ausgesucht: Das Format legt der Browser fest, seit den neunziger Jahren unverändert.
Was beim Abschicken über die Leitung geht
Ein <form method="post" action="/anmelden"> sammelt beim Klick auf den Knopf alle Felder ein, die
ein name-Attribut haben, und schickt sie als eine einzige Zeile los.
import express from "express";
const app = express();
// Ein Formular, damit du wirklich abschicken kannst. Genau das tut ein
// Browser, wenn jemand auf den Knopf drueckt.
app.get("/", (req, res) =>
res.type("html").send(
'<form method="post" action="/anmelden">' +
'<p><input name="name" value="Mario"></p>' +
'<p><label><input type="checkbox" name="newsletter" checked> Newsletter</label></p>' +
'<p><label><input type="checkbox" name="thema" value="js" checked> JavaScript</label></p>' +
'<p><label><input type="checkbox" name="thema" value="node" checked> Node</label></p>' +
"<p><button>Abschicken</button></p>" +
"</form>"
)
);
// Kein Parser angemeldet: hier kommt der Koerper roh an, so wie ihn der
// Browser beim Abschicken auf die Leitung legt.
app.post("/anmelden", (req, res) => {
let roh = "";
req.on("data", (stueck) => {
roh += stueck;
});
req.on("end", () => {
res.type("text/plain").send(
`Content-Type: ${req.headers["content-type"]}\nKoerper: ${roh}\n`
);
});
});
app.listen(3000); Rechts steht ein echtes Formular. Drück auf „Abschicken”, und in der Antwort steht Zeichen für Zeichen, was dein Browser gerade auf die Leitung gelegt hat.
Das ist die ganze Kodierung: Schlüssel, Gleichheitszeichen, Wert, dazwischen ein &. Sonderzeichen
werden verpackt wie in einer Adresse, ein Leerzeichen wird also zu + oder %20. Der Name dafür
steht in der Kopfzeile darüber: application/x-www-form-urlencoded.
Die Antwort zeigt dir den Körper nur, weil dieses Beispiel ihn ausdrücklich zurückschreibt. Ein
richtiger Server tut das nicht, ansehen kannst du ihn trotzdem: Im Reiter Netzwerk steht der
POST /anmelden, und aufgeklappt liest du dieselbe Zeile im Anfrage-Rumpf. Das ist dort die
Beschriftung für das, was dieser Kurs den Körper nennt.
Zwei Dinge lohnen sich zu merken. Erstens ist name an einem Eingabefeld nicht die Beschriftung,
sondern der Schlüssel im Körper. Ein Feld ohne name wird gar nicht erst mitgeschickt, egal was
drinsteht, und das ist einer der stilleren Fehler beim Bauen eines Formulars. Zweitens taucht thema
zweimal auf. Der Browser wirft nichts weg, wenn zwei Felder denselben Namen tragen.
Der Parser dafür heißt urlencoded
express.json() aus Lektion 10.3 hilft hier nicht weiter. Für Formulare gibt es
express.urlencoded({ extended: true }), angemeldet als Middleware, ganz oben, genau wie sein
Geschwister.
import express from "express";
const app = express();
app.use(express.urlencoded({ extended: true }));
// Ein Formular, damit du wirklich abschicken kannst. Genau das tut ein
// Browser, wenn jemand auf den Knopf drueckt.
app.get("/", (req, res) =>
res.type("html").send(
'<form method="post" action="/anmelden">' +
'<p><input name="name" value="Mario"></p>' +
'<p><label><input type="checkbox" name="newsletter" checked> Newsletter</label></p>' +
'<p><label><input type="checkbox" name="thema" value="js" checked> JavaScript</label></p>' +
'<p><label><input type="checkbox" name="thema" value="node" checked> Node</label></p>' +
"<p><button>Abschicken</button></p>" +
"</form>"
)
);
app.post("/anmelden", (req, res) => {
res.json({
body: req.body,
typVonThema: Array.isArray(req.body.thema) ? "Array" : typeof req.body.thema,
});
});
app.listen(3000); Dasselbe Formular, diesmal mit dem Parser davor. Nimm die Häkchen einzeln heraus und schick jedes Mal neu ab, dann siehst du die drei Fälle nacheinander.
Der erste Fall ist der Normalfall: ein Objekt mit den Feldnamen als Schlüssel. Interessant sind die beiden anderen.
Ein Feld ist einmal Text und einmal Liste
Schau dir thema in den drei Fällen an. Bei zwei angehakten Kästchen bekommst du ein Array, bei
einem einzigen einen ganz normalen String. Der Parser rät nicht, ob du eine Liste erwartest, er
berichtet nur, was angekommen ist.
Das ist die häufigste Überraschung in diesem Bereich, und sie ist heimtückisch, weil dein Code
trotzdem läuft. Wer die Anzahl mit req.body.thema.length bestimmt, bekommt bei zwei Themen eine 2
und freut sich. Bei einem einzigen Thema js bekommt er ebenfalls eine 2, denn das ist die Länge
der Zeichenkette. Der Fehler zeigt sich also ausgerechnet im einfachsten Fall.
Der Griff dagegen ist eine Zeile: [].concat(req.body.thema ?? []) macht aus keinem Wert eine leere
Liste, aus einem Wert eine Liste mit einem Eintrag und lässt eine Liste in Ruhe. Danach zählst du
gefahrlos.
Das Kästchen, das nichts schickt
Im dritten Fall fehlt newsletter komplett. Nicht false, nicht ein leerer String, sondern
der Schlüssel ist nicht da.
Ein Kontrollkästchen wird nur mitgeschickt, wenn es angehakt ist, und dann mit dem Wert aus seinem
value-Attribut, ohne Angabe schlicht mit on. Frag also nie if (req.body.newsletter === false),
sondern prüf auf Anwesenheit. Dieselbe Regel gilt für Auswahlfelder, bei denen nichts ausgewählt
wurde.
Der falsche Parser tut gar nichts
Und weil es so leicht passiert, hier noch der Fall, in dem alles richtig aussieht und trotzdem nichts ankommt.
import express from "express";
const app = express();
// Der falsche Parser: express.json() ist fuer application/json zustaendig
// und laesst einen Formularkoerper unberuehrt liegen.
app.use(express.json());
// Ein Formular, damit du wirklich abschicken kannst. Genau das tut ein
// Browser, wenn jemand auf den Knopf drueckt.
app.get("/", (req, res) =>
res.type("html").send(
'<form method="post" action="/anmelden">' +
'<p><input name="name" value="Mario"></p>' +
'<p><label><input type="checkbox" name="newsletter" checked> Newsletter</label></p>' +
'<p><label><input type="checkbox" name="thema" value="js" checked> JavaScript</label></p>' +
'<p><label><input type="checkbox" name="thema" value="node" checked> Node</label></p>' +
"<p><button>Abschicken</button></p>" +
"</form>"
)
);
app.post("/anmelden", (req, res) => {
res.json({ typ: typeof req.body, name: req.body?.name ?? null });
});
app.listen(3000); Wieder dasselbe Formular, wieder derselbe Knopf, und diesmal kommt nichts an.
req.body ist undefined, obwohl der Körper vollständig angekommen ist. Der Grund steht in den
beiden Zeilen darunter und ist derselbe wie in 10.3: Jeder Parser sieht sich zuerst den
Content-Type an und lässt liegen, wofür er nicht zuständig ist. Melde beide an, wenn dein Server
beides bedienen soll, sie stören sich nicht.
Eine dritte Art gibt es noch. Sobald ein Formular ein Feld für Dateien enthält, wechselt der Browser
auf multipart/form-data, weil eine Datei sich nicht sinnvoll in eine Zeile aus Gleichheitszeichen
pressen lässt. Express bringt dafür nichts mit, das übernimmt ein Paket. Datei-Uploads bleiben in
diesem Kurs draußen: Sie sind ein Thema für sich, vor allem beim Prüfen dessen, was da hochkommt.
Zum Mitnehmen
Ein Formular schickt kein JSON. Und ein nicht angehaktes Kästchen schickt nicht false, sondern gar nichts.
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.