mitmario.dev

Synthese: dieselbe Seite mit Express

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

Kein neuer Stoff in dieser Lektion. Stattdessen zweimal dieselbe Sache, und der Vergleich ist der ganze Inhalt.

Einmal so

Der Server aus Abschnitt 8
import { createServer } from "node:http";

const kurse = [
  { slug: "html", titel: "HTML" },
  { slug: "css", titel: "CSS" },
];

const server = createServer((req, res) => {
  const pfad = new URL(req.url, "http://localhost").pathname;

  if (req.method === "GET" && pfad === "/") {
    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end("<h1>Kurse</h1>");
    return;
  }

  if (req.method === "GET" && pfad === "/api/kurse") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ anzahl: kurse.length, kurse }));
    return;
  }

  if (req.method === "GET" && pfad.startsWith("/api/kurse/")) {
    const slug = pfad.slice("/api/kurse/".length);
    const kurs = kurse.find((eintrag) => eintrag.slug === slug);

    if (kurs === undefined) {
      res.writeHead(404, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ fehler: "Kurs nicht gefunden" }));
      return;
    }

    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(kurs));
    return;
  }

  res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
  res.end("nicht gefunden");
});

server.listen(3000);

Und einmal so

Derselbe Server mit Express
import express from "express";

const kurse = [
  { slug: "html", titel: "HTML" },
  { slug: "css", titel: "CSS" },
];

const app = express();

app.get("/", (req, res) => res.send("<h1>Kurse</h1>"));
app.get("/api/kurse", (req, res) => res.json({ anzahl: kurse.length, kurse }));

app.get("/api/kurse/:slug", (req, res) => {
  const kurs = kurse.find((eintrag) => eintrag.slug === req.params.slug);

  if (kurs === undefined) {
    return res.status(404).json({ fehler: "Kurs nicht gefunden" });
  }

  res.json(kurs);
});

app.use((req, res) => res.status(404).json({ fehler: "nicht gefunden" }));

app.listen(3000);

Beide Beispiele laufen, und du kannst zwischen ihnen hin und her wechseln: Der Reiter oben im Editor tauscht das Beispiel, und die Seite zieht nach. Ruf in beiden /, /api/kurse, /api/kurse/css und /nix auf, dann hast du den Vergleich viermal.

Dreimal kommt dieselbe Antwort, beim vierten Mal nicht. Der handgeschriebene Server sagt nicht gefunden als Text, weil ich das so geschrieben habe; die Express-Fassung antwortet mit JSON, weil ein eigener Standardfall dort eine Zeile ist und nicht fünf.

Was weg ist

Zähl nicht die Zeilen, zähl die Gelegenheiten, etwas falsch zu machen. Davon sind drei verschwunden.

Das vergessene res.end. In der Fassung von Hand endet jeder Zweig damit, und jeder Zweig braucht danach ein return. Acht Stellen, an denen ein fehlendes Wort die Anfrage hängen lässt. In der Express-Fassung gibt es weder das eine noch das andere.

Der fehlende Standardfall. Von Hand steht er ganz unten und ist leicht zu vergessen, und wenn er fehlt, hängt jede unbekannte Adresse. Mit Express ist einer schon da, und wenn du ihn ersetzen willst, ist das eine bewusste Zeile.

Der falsche Inhaltstyp. Von Hand wird er sechsmal gesetzt, und beim sechsten Mal schreibt man text/plan statt text/plain oder vergisst den Zeichensatz. Mit Express kommt er aus der Methode, die du ohnehin aufrufst.

Dazu kommt das Zerlegen des Pfads: startsWith plus slice plus die Prüfung, ob wirklich nur ein Stück folgt, gegen /api/kurse/:slug.

Was nicht weg ist

Was Express nicht abnimmt
import express from "express";

const kurse = [{ slug: "css", titel: "CSS" }];

const app = express();

// Beide Routen sind gueltig, Express beschwert sich nicht. Trotzdem ist
// die Reihenfolge falsch, und das merkt nur, wer es ausprobiert.
app.get("/api/kurse/:slug", (req, res) => {
  const kurs = kurse.find((eintrag) => eintrag.slug === req.params.slug);
  if (kurs === undefined) return res.status(404).json({ fehler: "nicht gefunden" });
  res.json(kurs);
});

app.get("/api/kurse/neu", (req, res) => res.json({ formular: true }));

app.listen(3000);

Hier ist alles richtig geschrieben. Beide Routen sind gültig, Express startet ohne Murren, und trotzdem steht rechts auf /api/kurse/neu ein 404 für einen Kurs, der nie gemeint war. /api/kurse/css funktioniert dabei tadellos, der Fehler zeigt sich also nur an der einen Adresse.

Express nimmt dir Tipparbeit und Flüchtigkeitsfehler ab. Es nimmt dir keine Entscheidung ab. Welche Route es gibt, in welcher Reihenfolge sie stehen, welcher Statuscode passt, was bei einem Fehler zurückkommt: alles davon ist weiterhin deine Sache, und keine Bibliothek der Welt kann das für dich beantworten.

Der Grund, warum dieser Kurs den Umweg genommen hat

Du hättest mit Kapitel 9 anfangen können. Vier Zeilen, ein laufender Server, und in einer Nachmittagsstunde eine kleine API. Das ist der Weg, den die meisten Anleitungen nehmen.

Der Preis dafür zeigt sich erst später, und zwar immer am selben Punkt: wenn etwas nicht funktioniert. Warum kommt die Antwort als HTML, wenn ich JSON erwarte? Warum hängt die Anfrage? Warum trifft meine Route nicht? Wer nur app.get kennt, hat auf keine dieser Fragen einen Ansatz, weil er nicht weiß, was app.get eigentlich tut.

Du weißt es. res.json ist setHeader plus JSON.stringify plus end. Eine Route ist ein Vergleich von Methode und Pfad. Der Standardfall ist das letzte if. req und res sind dieselben Objekte wie in Abschnitt 8.

Wer den handgeschriebenen Server nicht verstanden hat, versteht den Express-Server auch nicht. Er merkt es nur später.

Zum Mitnehmen

Dieselbe Aufgabe, halb so viele Zeilen, drei Fehlerquellen weniger. Die Entscheidung, welche Route was tut, gehört weiterhin dir.

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.