mitmario.dev

Synthese: die eigene Kette

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

Fünf Lektionen, fünf Bausteine. Jetzt liegen sie alle auf dem Tisch, und die Frage ist nur noch: in welcher Reihenfolge?

Die sechs Glieder

Die Kette, die fast jede Anwendung hat
import express from "express";
import apiRouter from "./routen/api.js";

const app = express();

// 1. Protokoll ganz oben: sieht alles, auch was spaeter scheitert.
app.use((req, res, next) => {
  res.on("finish", () => console.error(`${req.method} ${req.originalUrl} ${res.statusCode}`));
  next();
});

// 2. Koerper lesen, bevor irgendeine Route ihn braucht.
app.use(express.json());

// 3. Statische Dateien: was hier liegt, braucht keine Route.
app.use(express.static("public"));

// 4. Die eigenen Routen.
app.use("/api", apiRouter);

// 5. Standardfall: greift, wenn bis hierhin niemand zustaendig war.
app.use((req, res) => res.status(404).json({ fehler: "nicht gefunden" }));

// 6. Fehler-Handler ganz unten, mit vier Argumenten.
app.use((fehler, req, res, next) => {
  console.error(fehler.message);
  res.status(500).json({ fehler: "Serverfehler" });
});

app.listen(3000);

Rechts steht die Startseite aus public/, und niemand hat eine Route dafür geschrieben. /api/buecher und /nix holst du über die Adresszeile, den POST über das Terminal:

curl -i -X POST -H "Content-Type: application/json" -d '{"titel":"Neu"}' http://localhost:3000/api/buecher

Diese Reihenfolge findest du in unzähligen Express-Projekten, und sie ist kein Zufall. Jede Position hat einen Grund.

Das Protokoll steht oben, weil es alles sehen soll. Auch die Anfragen, die gleich im 404 landen, und besonders die, die einen Fehler auslösen. Ein Protokollant weiter unten sieht nur noch das, was bis dorthin durchgekommen ist, und genau die Fälle, die dich interessieren, kommen nicht so weit.

Das Lesen des Bodys steht über den Routen, weil req.body sonst leer ist, wenn eine Route ihn braucht. Du hast das in Lektion 10.3 gesehen, und es ist der häufigste Grund, warum eine POST-Route partout nichts empfängt.

Statische Dateien stehen über den Routen, damit eine Datei nicht erst durch die ganze Routenlogik läuft, bevor sie ausgeliefert wird. Hier gilt aber die Ausnahme aus Lektion 9.5: Wenn eine Route dieselbe Adresse wie eine Datei bedienen soll, gewinnt, was oben steht. Dann drehst du die beiden Zeilen bewusst um und schreibst dir einen Kommentar dazu.

Die Routen stehen in der Mitte, weil sie die eigentliche Arbeit sind.

Der Standardfall steht unter allem, was antworten könnte. Er passt auf jede Anfrage, deshalb darf er erst dran sein, wenn wirklich niemand mehr zuständig war.

Der Fehler-Handler steht ganz unten, weil nur er von dort etwas entgegennehmen kann. Alles, was über ihm schiefgeht, landet bei ihm. Alles darunter gäbe es nicht.

Was passiert, wenn du eins verschiebst

Der Standardfall an der falschen Stelle
import express from "express";
import apiRouter from "./routen/api.js";

const app = express();

// Der Standardfall steht zu weit oben. Er passt auf jede Anfrage, also ist ab
// hier alles ein 404, und niemand merkt es an einer Fehlermeldung.
app.use((req, res) => res.status(404).json({ fehler: "nicht gefunden" }));

app.use(express.static("public"));
app.use("/api", apiRouter);

app.listen(3000);

Dieselbe Anwendung, eine Zeile verschoben. Rechts steht jetzt kein <h1>Buecherei</h1> mehr, sondern ein 404, und /api/buecher gibt denselben. Kein Absturz, keine Warnung, keine Fehlermeldung. Der Server läuft, er tut nur nichts mehr.

Das ist die Sorte Fehler, die einen Nachmittag kostet, wenn man das Prinzip nicht kennt, und dreißig Sekunden, wenn man es kennt. Wenn plötzlich alles einen 404 gibt, obwohl die Routen richtig aussehen, steht etwas über ihnen, das nicht dort hingehört.

Eine hängende Anfrage finden

Der zweite typische Fall ist der aus Lektion 10.1: Es kommt gar keine Antwort.

So findest du eine hängende Anfrage
import express from "express";

const app = express();

// Wenn eine Anfrage haengt, protokollierst du jedes Glied einzeln. Die letzte
// Zeile, die noch kommt, steht direkt ueber der Stelle mit dem Fehler.
const protokoll = [];

// Diese Route steht ganz oben, damit du das Protokoll auch dann noch
// abrufen kannst, wenn die Kette darunter haengt.
app.get("/protokoll", (req, res) => res.json(protokoll));

app.use((req, res, next) => {
  protokoll.push("1 Protokoll");
  next();
});

app.use((req, res, next) => {
  protokoll.push("2 Koerper lesen");
  next();
});

app.use((req, res, next) => {
  protokoll.push("3 Anmeldung pruefen");
  // Hier fehlt next(). Weiter kommt nichts.
});

app.use((req, res, next) => {
  protokoll.push("4 Routen");
  next();
});

app.listen(3000);

Die Methode ist unspektakulär und funktioniert immer: Schreib in jedes Glied eine Ausgabe mit einer Nummer und schick eine Anfrage. Die letzte Zeile, die noch erscheint, steht direkt über der Stelle, an der es klemmt.

Im Reiter „Browser” hängt die Anfrage auf /daten, die Seite lädt und lädt. Tipp jetzt /protokoll in die Adresszeile: Dort steht die Liste, und sie hört nach Glied 3 auf. Genau dort fehlt das next(), Glied 4 wird nie erreicht.

Die Route für das Protokoll steht in diesem Beispiel ganz oben, sonst hinge sie mit. In einer echten Anwendung schreibst du statt einer Liste einfach console.log und schaust ins Terminal.

Dieselbe Technik hilft übrigens auch bei dem Fall darüber. Kommt eine Zeile nicht mehr, weil weiter oben schon geantwortet wurde, siehst du das an derselben Nummernfolge.

Zwei Fragen, die dir die Position verraten

Wenn du eine neue Middleware einsortierst, reichen meistens zwei Fragen.

Was muss vorher schon passiert sein? Eine Middleware, die req.body liest, gehört unter express.json(). Eine, die die Anmeldung prüft, gehört über die Routen, die sie schützen soll.

Soll sie auch dann noch laufen, wenn schon geantwortet wurde? Wenn ja, kann sie nicht in der Kette stehen, sondern gehört in ein res.on("finish", ...). Das ist der Grund, warum die Zeitmessung aus Lektion 10.2 zweigeteilt ist.

In der Challenge baust du die Kette komplett. Zwei Glieder schreibst du selbst, den Rest hängst du ein, und die Reihenfolge ist die eigentliche Aufgabe.

Zum Mitnehmen

Sechs Glieder, und jede Position hat einen Grund. Protokoll oben, Fehler-Handler unten, Standardfall knapp darüber.

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.