Abschnitt 10 · Lektion 4
Router
Bis hierhin lag alles in einer Datei. Bei fünf Routen geht das gut. Bei fünfzig nicht mehr, und fünfzig sind in einer echten Anwendung schnell erreicht.
Express hat dafür express.Router().
Eine kleine Anwendung für sich
import express from "express";
const buecher = [
{ id: 1, titel: "Buch 1" },
{ id: 2, titel: "Buch 2" },
];
// Ein Router ist eine kleine Anwendung fuer sich: eigene Routen, eigene
// Middleware. Der Pfad, unter dem er haengt, kommt hier nicht vor.
const router = express.Router();
router.get("/", (req, res) => res.json({ anzahl: buecher.length }));
router.get("/:id", (req, res) => {
const buch = buecher.find((eintrag) => eintrag.id === Number(req.params.id));
if (buch === undefined) return res.status(404).json({ fehler: "Buch nicht gefunden" });
res.json(buch);
});
export default router; import express from "express";
import buecherRouter from "./buecher.js";
const app = express();
// Hier und nur hier steht, unter welcher Adresse der Router haengt.
app.use("/buecher", buecherRouter);
app.listen(3000); express.Router() gibt dir ein Objekt, das sich fast genau wie app verhält: Es kennt get,
post, use und alles andere. Nur horchen kann es nicht, es hat kein listen. Dafür lässt es sich
mit app.use(pfad, router) an einer Stelle einhängen.
Rechts steht /buecher, also die Route, die in buecher.js unter "/" angemeldet ist. Probier
/buecher/2 und /buecher/99 in der Adresszeile, dann hast du beide Routen und den Fehlerfall.
Schau auf die Pfade in buecher.js. Dort steht "/" und "/:id", nicht "/buecher" und
"/buecher/:id". Der gemeinsame Anfang ist weg, weil er beim Einhängen dazukommt. Das ist derselbe
Mechanismus wie bei app.use("/api", ...) aus Lektion 10.2, und deshalb sagt req.url innerhalb
eines Routers auch nur den Rest.
Warum das mehr ist als Aufräumen
Der offensichtliche Gewinn: Die Datei ist kürzer und du findest die Bücher-Routen dort, wo Bücher draufsteht.
Der weniger offensichtliche ist wichtiger.
import express from "express";
const buecher = [
{ id: 1, titel: "Buch 1" },
{ id: 2, titel: "Buch 2" },
];
const router = express.Router();
router.get("/", (req, res) => res.json({ anzahl: buecher.length }));
router.get("/:id", (req, res) => {
const buch = buecher.find((eintrag) => eintrag.id === Number(req.params.id));
if (buch === undefined) return res.status(404).json({ fehler: "Buch nicht gefunden" });
res.json(buch);
});
export default router; import express from "express";
import buecherRouter from "./buecher.js";
const app = express();
// Derselbe Router, zweimal eingehaengt. In buecher.js steht kein einziges
// Mal das Wort buecher oder archiv.
app.use("/buecher", buecherRouter);
app.use("/archiv", buecherRouter);
app.listen(3000); Rechts steht /buecher/1. Tipp /archiv/1 in die Adresszeile, und es kommt dieselbe Antwort aus
derselben Datei.
In buecher.js steht das Wort buecher nicht ein einziges Mal in einem Pfad. Die Datei weiß nicht,
wo sie hängt, und genau deshalb kann sie zweimal hängen. Wenn du morgen alles unter /api
verschieben willst, änderst du eine Zeile in server.js und keine einzige in den Router-Dateien.
Das ist die Sorte Entkopplung, die man erst zu schätzen weiß, wenn man einmal fünfzig Pfade von Hand umgeschrieben hat.
Die Aufteilung, die sich bewährt hat
Eine Datei je Ressource. buecher.js für alles unter /buecher, autoren.js für alles unter
/autoren, nutzer.js für alles unter /nutzer. Dazu eine server.js, die sie einhängt und sonst
wenig tut.
Wenn du dir das aus Abschnitt 2 bekannt vorkommt: ja. Ein Router ist genau das, was ein Modul exportieren sollte. Ein Ding, ein Zweck, eine klare Schnittstelle nach außen, und der Rest bleibt drinnen. Der Kurs hat dir das damals an einem Rechner-Modul gezeigt, hier ist derselbe Gedanke an einer Stelle, an der er sich sofort auszahlt.
Middleware am Router
import express from "express";
const router = express.Router();
// Diese Middleware haengt am Router, nicht an der Anwendung. Sie gilt fuer
// alles in dieser Datei und fuer nichts sonst.
router.use((req, res, next) => {
if (req.headers["x-schluessel"] !== "geheim") {
return res.status(401).json({ fehler: "nicht angemeldet" });
}
next();
});
router.get("/zahlen", (req, res) => res.json({ zahlen: [1, 2, 3] }));
export default router; import express from "express";
import internRouter from "./intern.js";
const app = express();
app.get("/", (req, res) => res.send("offen fuer alle"));
app.use("/intern", internRouter);
app.listen(3000); router.use(...) hängt eine Middleware an diesen Router. Sie gilt für alle seine Routen und für
keine andere. Die Startseite bleibt offen, alles unter /intern verlangt den Schlüssel. Rechts steht
die offene Seite, /intern/zahlen gibt einen 401, und mit Schlüssel geht es:
curl -i -H "X-Schluessel: geheim" http://localhost:3000/intern/zahlen
Merk dir dieses Beispiel, es kommt wieder. In Abschnitt 14 baust du eine Anmeldung, und die Frage
lautet dann: Welche Teile der Anwendung sind geschützt und welche nicht? Die Antwort ist eine
einzige router.use-Zeile in der richtigen Datei, statt einer Prüfung in jeder Route.
Und das ist der eigentliche Grund, warum sich das Aufteilen lohnt. Eine Schranke, die für zwölf Routen gelten soll, gehört an eine Stelle. Wenn sie zwölfmal dasteht, wird sie irgendwann elfmal dastehen, und niemand merkt es.
Womit du rechnen musst
Zwei Kleinigkeiten, die beim ersten Router gern schiefgehen.
Der Router muss exportiert und importiert werden. export default router am Ende der Datei,
und in server.js ein passender Import. Vergisst du den Export, bekommst du beim Start einen
Fehler, der auf app.use zeigt und nicht auf die Ursache.
Die Reihenfolge gilt weiterhin. Ein Router ist ein Eintrag in derselben Liste wie alles andere.
Steht dein Standardfall über app.use("/buecher", ...), kommen die Bücher nie dran. Was du in
Lektion 10.1 gelernt hast, gilt hier unverändert, nur dass jetzt ein ganzes Bündel Routen auf einmal
an einer Position hängt.
Zum Mitnehmen
Ein Router ist eine kleine Anwendung für sich. Unter welcher Adresse er hängt, steht an genau einer Stelle: beim Einhä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.