Abschnitt 9 · Lektion 4
Antworten senden
In Abschnitt 8 war eine Antwort immer dieselbe Handarbeit: Status setzen, Inhaltstyp setzen, umwandeln, senden. Express bietet dir für jeden üblichen Fall eine Methode an.
Die fünf, die du brauchst
import express from "express";
const app = express();
app.get("/text", (req, res) => res.send("nur Text"));
app.get("/daten", (req, res) => res.json({ anzahl: 2 }));
app.get("/fehlt", (req, res) => res.status(404).json({ fehler: "nicht da" }));
app.get("/leer", (req, res) => res.sendStatus(204));
app.get("/woanders", (req, res) => res.redirect("/text"));
app.listen(3000); res.json(daten)ist der Normalfall für alles, was Daten sind. Es wandelt um und setzt den Inhaltstyp.res.send(text)ist der Normalfall für Text und HTML.res.status(code)setzt den Statuscode und gibtreszurück, deshalb lässt es sich davor ketten:res.status(404).json(...).res.sendStatus(code)schickt einen Code ohne Inhalt. Der übliche Weg für die 204 nach einem erfolgreichen Löschen.res.redirect(ziel)schickt eine Weiterleitung, standardmäßig mit 302.
Alle fünf stehen in derselben Datei. Rechts liegt /text, den Rest holst du über die Adresszeile,
und /woanders macht dabei genau das, was eine Weiterleitung soll: Der Browser landet auf /text,
und in der Adresszeile steht danach /text. Wie die Weiterleitung selbst aussieht, zeigt das
Terminal, denn curl folgt ihr nicht von allein:
curl -i http://localhost:3000/woanders
curl -i http://localhost:3000/leer
Eine Kleinigkeit an der ersten Route verdient einen Blick: res.send mit einer
Zeichenkette antwortet als text/html, nicht als text/plain. Das ist eine alte Festlegung von
Express und meistens genau richtig, weil man dort Markup hineinschreibt. Wenn du wirklich reinen
Text schicken willst, setz den Typ selbst mit res.type("text/plain") davor.
Was res.json abnimmt
import express from "express";
const app = express();
// So ging es in 8.4, von Hand. In Express geht es immer noch, denn
// res ist dieselbe ServerResponse wie dort.
app.get("/von-hand", (req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.end(JSON.stringify({ anzahl: 2 }));
});
// Und so geht es jetzt.
app.get("/mit-express", (req, res) => res.json({ anzahl: 2 }));
app.listen(3000); Zwei Routen, dieselbe Antwort, Zeichen für Zeichen. Die eine Fassung ist die aus Lektion 8.4, die andere ein Aufruf. Vergleich die beiden Köpfe im Terminal, sie sind gleich:
curl -i http://localhost:3000/von-hand
curl -i http://localhost:3000/mit-express
Das ist der Punkt, an dem sich die Handarbeit aus Abschnitt 8 auszahlt: Du siehst nicht nur, dass
res.json kürzer ist, sondern welche drei Zeilen es ersetzt. Und du weißt, dass du den
Inhaltstyp jederzeit selbst überschreiben kannst, weil res.setHeader immer noch da ist.
Eine Antwort pro Anfrage
Das ist die Regel, an der in diesem Abschnitt die meisten hängenbleiben, und sie klingt so selbstverständlich, dass man sie überliest.
Eine HTTP-Antwort besteht aus Statuszeile, Kopfzeilen, Leerzeile und Inhalt, in dieser Reihenfolge.
Sobald das erste Zeichen unterwegs ist, ist der Kopf weg. Ein zweites res.json müsste also
Kopfzeilen setzen, die längst gesendet sind, und deshalb wirft es ERR_HTTP_HEADERS_SENT. Genau
derselbe Fehler wie in Lektion 8.4, nur mit einer anderen Ursache.
Und diese Ursache ist fast immer dieselbe.
import express from "express";
const app = express();
// Das Protokoll, das auf einem echten Server niemand liest. Hier
// liegt es unter /log, damit du hineinschauen kannst.
const protokoll = [];
app.get("/teilen", (req, res) => {
const b = Number(req.query.b);
if (b === 0) {
// Hier fehlt ein return.
res.status(400).json({ fehler: "durch null teilen geht nicht" });
}
res.json({ ergebnis: 10 / b });
});
app.get("/log", (req, res) => res.json({ protokoll }));
// Faengt den Fehler ab, damit hier nur sein Code steht und kein
// Stacktrace mit Dateipfaden.
app.use((fehler, req, res, next) => {
protokoll.push(fehler.code);
});
app.listen(3000); Was dabei herauskommt, ist der Grund, warum dieser Fehler so schwer zu finden ist.
Von außen sieht alles richtig aus. Rechts steht /teilen?b=0 mit seinem 400 und der passenden
Meldung. Der Aufrufer merkt nichts, ein Test auf den Statuscode besteht, die Anwendung funktioniert
scheinbar. Nur im Log steht eine Fehlermeldung, und die schaut sich niemand an, solange nichts
kaputt ist. In diesem Beispiel kannst du es: Ruf /log auf, und dort steht
ERR_HTTP_HEADERS_SENT. Mit ?b=2 passiert dagegen nichts, das Protokoll bleibt leer.
Was tatsächlich passiert ist: Die Prüfung hat geantwortet, aber die Funktion lief danach weiter,
denn res.status(400).json(...) ist ein Aufruf und kein Sprung. Unten kam dann res.json und
versuchte ein zweites Mal zu antworten.
Ein return davor löst es, und zwar wirklich nur das eine Wort. return res.status(400).json(...)
ist die übliche Schreibweise dafür. Der Rückgabewert interessiert niemanden, das return steht dort
allein, um die Funktion zu verlassen.
Wenn du den Fehler irgendwann im Log siehst, such deshalb nicht nach der Stelle, an der er geworfen wird. Such nach dem Zweig darüber, der geantwortet hat, ohne aufzuhören.
Zum Mitnehmen
res.json für Daten, res.send für Text, res.status davor gekettet. Und pro Anfrage genau eine Antwort, sonst steht ERR_HTTP_HEADERS_SENT im Log.
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.