Abschnitt 10 · Lektion 1
Was ist Middleware?
In Abschnitt 9 hast du app.use schon zweimal benutzt, ohne dass ich erklärt habe, was es
eigentlich tut: einmal für den Standardfall ganz unten, einmal für express.static. Beide Male war
es dasselbe Werkzeug, und dieses Werkzeug hat einen Namen.
Eine Middleware ist eine Funktion, die zwischen der Anfrage und der Antwort sitzt. Express sammelt alle, die du anmeldest, in einer Liste und arbeitet sie von oben nach unten ab.
Ein Formular, das über mehrere Schreibtische wandert
Stell dir einen Antrag vor, der durch ein Büro geht. Der erste Schreibtisch trägt ihn ins Eingangsbuch ein und schiebt ihn weiter. Der zweite prüft, ob alles ausgefüllt ist, macht einen Haken dran und schiebt ihn weiter. Der dritte bearbeitet ihn und schickt eine Antwort zurück.
Drei Stellen, jede darf etwas ergänzen, jede darf zurückweisen, und wer nichts zu tun hat, reicht einfach weiter.
import express from "express";
const app = express();
// Erster Schreibtisch: sieht jede Anfrage und reicht weiter.
app.use((req, res, next) => {
console.error(`${req.method} ${req.url}`);
next();
});
// Zweiter Schreibtisch: ergaenzt etwas und reicht weiter.
app.use((req, res, next) => {
req.geprueft = true;
next();
});
// Dritter Schreibtisch: antwortet und reicht nichts mehr weiter.
app.get("/daten", (req, res) => {
res.json({ geprueft: req.geprueft === true, werte: [1, 2, 3] });
});
app.listen(3000); Schau dir die Signatur an: (req, res, next). Die ersten beiden kennst du aus Abschnitt 8 und 9.
Das dritte ist neu, und es ist der ganze Unterschied.
next() heißt: ich bin fertig, mach du weiter. Es antwortet nicht, es beendet nichts, es gibt
nur den Staffelstab an den nächsten Eintrag in der Liste.
Beachte auch, dass der zweite Schreibtisch sein Ergebnis an req hängt. Das ist kein Zufall,
sondern das übliche Muster: req ist das Objekt, das die ganze Kette entlangwandert, und alles, was
später jemand wissen soll, gehört daran. Lektion 10.2 geht darauf genauer ein.
Was passiert, wenn niemand weitergibt
Und jetzt der Fall, für den du diese Lektion wirklich brauchst.
import express from "express";
const app = express();
app.use((req, res, next) => {
console.log("Schreibtisch 1: weitergereicht");
next();
});
// Weder eine Antwort noch next(). Hier endet die Anfrage, ohne zu enden.
app.use((req, res, next) => {
console.log("Schreibtisch 2: liegen geblieben");
});
app.get("/daten", (req, res) => {
console.log("Schreibtisch 3: nie erreicht");
res.json({ werte: [1, 2, 3] });
});
app.listen(3000); Der zweite Schreibtisch ruft weder next() auf noch antwortet er. Damit passiert schlicht
nichts. Kein Fehler, keine Warnung, kein Absturz. Die Anfrage liegt da und wartet, bis der
Aufrufer aufgibt. Im Reiter „Browser” siehst du genau das: Die Seite lädt und hört nicht auf damit.
Das ist derselbe Fehler wie das vergessene res.end aus Lektion 8.1, nur mit einem anderen Gesicht.
Und beide äußern sich gleich: Der Browser dreht sein Rädchen, curl läuft in seinen Timeout, und
nichts von beidem sagt dir, was los ist. Schlimmer noch: Dein Protokoll sagt es auch nicht. Der erste
Schreibtisch hat die Anfrage ja gesehen und ordentlich notiert, und danach hört es einfach auf.
In der Challenge unten kannst du dir das ansehen, denn dort läuft dein Server wirklich. Nimm das
next() aus dem pruefer heraus und tipp im Terminal:
curl -s -S -m 2 http://localhost:3000/daten
Das -m 2 heißt „gib nach zwei Sekunden auf”, sonst wartest du, bis dir langweilig wird. Und genau
dahin läuft der Aufruf: curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received. Im Terminal steht derweil GET /daten, also die Zeile des Protokollanten.
Genau diese Kombination ist das Erkennungszeichen: Die Anfrage ist angekommen, sie ist
protokolliert, und trotzdem kommt nichts zurück. Setz das next() wieder ein, und derselbe Befehl
antwortet sofort mit {"geprueft":true,"werte":[1,2,3]}.
Merk dir die Diagnose, sie erspart dir viel Sucherei: Wenn eine Anfrage hängt, fehlt irgendwo ein
next() oder eine Antwort. Du findest die Stelle, indem du von oben Zeile für Zeile eine Ausgabe
einbaust und schaust, wo die letzte noch kommt.
Ohne eine einzige eingebaute Ausgabe geht es auch, nämlich im Reiter „Debug”. Drück dort auf
Aufzeichnen, und die Schritte stehen je Anfrage untereinander. In der Challenge unten sind es
bei richtiger Reihenfolge fünf, und sie laufen durch alle drei Funktionen: erst der Protokollant,
dann der Prüfer, dann der Antworter. Nimmst du das next() aus dem Prüfer heraus, sind es drei,
der letzte steht im Prüfer, und danach kommt nichts mehr. Und im Ausgangsstand, wo der Antworter
oben steht, ist es genau ein Schritt: Er antwortet, und die beiden anderen kommen nie dran.
Dass die Liste aufhört, ist hier die ganze Auskunft.
Eine Route ist auch nur eine Middleware
app.get("/daten", ...) sieht aus wie etwas völlig anderes als app.use(...). Ist es aber nicht.
Beides trägt einen Eintrag in dieselbe Liste ein. Der einzige Unterschied: Der app.get-Eintrag
kommt nur dran, wenn Methode und Pfad passen, der app.use-Eintrag bei jeder Anfrage.
Deshalb liegen die beiden viel näher beieinander, als ihre Namen vermuten lassen. Und deshalb gilt für beide dieselbe Regel.
Die Reihenfolge ist die ganze Semantik
Oben steht, was zuerst passiert. Dieser eine Satz erklärt fast alles, was dir mit Middleware schiefgehen kann.
import express from "express";
const app = express();
// Das Protokoll liegt hier in einer Liste statt im Log, damit du es
// unter /protokoll nachlesen kannst.
const protokoll = [];
// Derselbe Protokollant, zweimal notiert. Einmal ganz oben.
app.use((req, res, next) => {
protokoll.push(`oben sieht ${req.url}`);
next();
});
app.get("/daten", (req, res) => res.json({ werte: [1, 2, 3] }));
app.get("/protokoll", (req, res) => res.json(protokoll));
// Und einmal ganz unten, unter der Route.
app.use((req, res, next) => {
protokoll.push(`unten sieht ${req.url}`);
next();
});
app.listen(3000); Ruf nacheinander /daten und /nix auf und danach /protokoll. Dort steht, was die beiden gesehen
haben, und es ist nicht dasselbe.
Zweimal derselbe Code, zweimal derselbe Aufruf, und trotzdem sehen die beiden völlig
Verschiedenes. Der obere protokolliert beide Anfragen. Der untere sieht nur /nix, denn bei
/daten hat die Route darüber schon geantwortet und die Kette endet dort.
Ein Protokollant ganz oben sieht alles. Ganz unten sieht er nur noch das, was durchgefallen ist. Beides kann richtig sein, aber du musst wissen, was du willst.
Nebenbei siehst du auf /nix, dass die Anfrage trotz des unteren next() mit 404 endet: Nach
dem letzten Eintrag in der Liste kommt Express’ eingebauter Standardfall, den du in Lektion 9.2
ersetzt hast.
Warum das Express zu einem Baukasten macht
Ohne dieses Konzept wäre Express eine Bibliothek, die Pfade auf Funktionen abbildet, und mehr nicht.
Mit ihm ist es ein Bausatz: Protokollierung, Body lesen, statische Dateien, Anmeldung, Zeitmessung,
Fehlerbehandlung. Alles davon ist eine Funktion mit drei Argumenten, alles davon meldest du mit
app.use an, und die Reihenfolge entscheidest du.
Genau das machen die nächsten fünf Lektionen. Und beim ersten Mal, wenn du in einer fremden
Express-Anwendung einen Stapel app.use-Zeilen siehst, weißt du jetzt, dass du ihn von oben nach
unten lesen musst.
Zum Mitnehmen
Eine Middleware bekommt drei Argumente, und das dritte ist der Unterschied: next gibt weiter. Wer weder next aufruft noch antwortet, lässt die Anfrage hängen.
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.