mitmario.dev

Synthese: der handgeschriebene Server

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

Sechs Lektionen, sechs Bausteine. Diese hier setzt sie zusammen, und danach hast du einen Server, der eine Seite und eine Datenschnittstelle bedient, ganz ohne fremden Code.

Der Ablauf, jedes Mal derselbe

  1. Server starten mit createServer und listen.
  2. Anfrage zerlegen in Methode und Pfad.
  3. Route finden, also die passende Bedingung treffen.
  4. Status und Kopfzeilen setzen, bevor der Inhalt losgeht.
  5. Inhalt senden.
  6. Abschließen mit res.end(), und zwar auf jedem Weg durch die Funktion.
Die sechs Bausteine in einer Datei
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("<!doctype html><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;
  }

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

server.listen(3000);

Zwei Routen und ein Standardfall, und du erkennst jede Zeile darin. Das ist der ganze Abschnitt in gut dreißig Zeilen.

Rechts steht die Seite, und sie ist eine Seite: eine Überschrift, keine Zeichen. Das ist die erste Stelle im Kurs, an der du text/html ausgelieferst hast, und der Unterschied zu text/plain steht direkt vor dir. Tipp /api/kurse in die Adresszeile, und dort steht das JSON. Beides kommt aus derselben Datei, nur mit einer anderen Kopfzeile.

curl -i http://localhost:3000/gibtsnicht

Merk dir, wo es unangenehm wird

Das ist der eigentliche Zweck dieser Lektion. Der Server oben funktioniert, aber an drei Stellen merkst du beim Schreiben, dass du Arbeit machst, die eigentlich niemand machen sollte.

Erstens: Platzhalter im Pfad. /api/kurse/css von Hand zu zerlegen hast du in 8.5 gemacht, mit split und einer Längenprüfung. Für eine Route geht das. Für zehn Routen mit unterschiedlich vielen Platzhaltern schreibst du dir eine Mustererkennung.

Zweitens: Inhaltstypen für Dateien.

Die Tabelle der Inhaltstypen
import { extname } from "node:path";

// Die Tabelle, die dir spaeter jemand abnimmt.
const typen = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".png": "image/png",
  ".svg": "image/svg+xml",
};

for (const datei of ["seite.html", "stil.css", "logo.png", "daten.csv"]) {
  const endung = extname(datei);
  console.log(`${datei} -> ${typen[endung] ?? "application/octet-stream"}`);
}

Wenn dein Server nicht nur eine Seite, sondern auch Stylesheets, Bilder und Schriften ausliefern soll, brauchst du diese Zuordnung für jede Endung, die vorkommt. Die vollständige Tabelle hat mehrere hundert Einträge. Niemand tippt die ab, und niemand sollte.

Drittens: die immer gleichen drei Zeilen. Status setzen, Inhaltstyp setzen, umwandeln und senden. In jeder einzelnen Route.

Der eigene kleine Helfer
import { createServer } from "node:http";

// Statt in jeder Route dieselben drei Zeilen: ein Helfer.
const sendeJson = (res, status, daten) => {
  res.writeHead(status, { "Content-Type": "application/json" });
  res.end(JSON.stringify(daten));
};

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

  if (pfad === "/api/zeit") {
    sendeJson(res, 200, { stunde: 12 });
    return;
  }

  if (pfad === "/api/kurse") {
    sendeJson(res, 200, { anzahl: 2 });
    return;
  }

  sendeJson(res, 404, { fehler: "nicht gefunden" });
});

server.listen(3000);

Drei Routen, drei Aufrufe, und der Aufbau der Antwort steht nur noch an einer Stelle. Probier /nix in der Adresszeile, dann siehst du, dass auch der Standardfall durch denselben Helfer geht.

Und hier passiert etwas Interessantes: Der naheliegende Ausweg ist ein eigener Helfer. Der ist auch gut, er spart sofort Zeilen und Flüchtigkeitsfehler. Aber schau ihn dir an und denk einen Schritt weiter. Wenn du als Nächstes einen Helfer für HTML schreibst, dann einen für Weiterleitungen, dann eine Funktion, die Pfade mit Platzhaltern vergleicht, und schließlich eine Liste von Routen statt einer if-Kette, dann hast du irgendwann Express gebaut. Nur schlechter getestet.

Das ist keine Kritik an diesem Abschnitt

Die drei Punkte oben sind keine Mängel deines Servers. Sie sind eine Liste, und Abschnitt 9 arbeitet genau sie ab: Routing mit Platzhaltern, Inhaltstypen von selbst, JSON in einem Aufruf, statische Dateien aus einem Ordner.

Der Unterschied zwischen dir und jemandem, der direkt mit Express angefangen hat, ist ab jetzt: Du weißt bei jeder dieser Erleichterungen, was sie ersetzt. app.get("/api/kurse/:slug", ...) ist für dich keine Magie, sondern eine Bedingung, ein split und eine Längenprüfung, die jemand anderes schon geschrieben hat.

In Lektion 9.6 baust du genau die Datei aus dieser Lektion noch einmal mit Express. Heb sie dir gut auf, der Vergleich der beiden ist die halbe Botschaft des nächsten Abschnitts.

Zum Mitnehmen

Sechs Bausteine, und du kennst jeden einzeln. Was jetzt noch unangenehm ist, ist genau die Liste, die Abschnitt 9 abarbeitet.

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.