[[PageOutline(2-5)]] = Express = == Literaturhinweis == Roden: [[https://ebookcentral.proquest.com/lib/hrw/reader.action?docID=1049737&ppg=182|Node.js & Co. - Kapitel 10]] == Einführung == [[https://expressjs.com|Express]] ist ein Framework zur Erstellung von Webanwendungen auf einem Node.js Server. Es wird mit npm installiert {{{ > npm install express }}} und als Modul in Node.js Anwendungen eingebunden: {{{ #!javascript var express = require('express'); }}} Mit Express lassen sich REST-Interfaces deutlich komfortabler implementieren, als mit den Bordmitteln von Node.js. Insbesondere ist es möglich, Eventhandler für Requests und Gruppen von Requests voneinander nach der HTTP-Methode und den angesprochenen Objekten getrennt zu implementieren. Express ist Bestandteil des MEAN-Stacks bestehens aus * '''M'''ongoDB, * '''E'''xpress, * '''A'''ngularJS und * '''N'''ode.js, mit dem sich !JavaScript-basierte Webanwendungen sehr komfortabel implementieren lassen. == statischer Dateiserver == Express stellt einen einfach einzubindenden statischen Dateiserver zur Verfügung. Auf diese Weise kann der HTML/CSS/JS-Client durch den Node.js Server ausgeliefert werden. Für den statischen Dateiserver muss ein Verzeichnis angegeben werden, das ähnlich dem htdocs-Verzeichnis des Apache-Servers als Server-root fungiert (vollständiges Beispiel in [[source:Web_Quellcode_FP/vorlesung/express/hello_fileserver.js|hello_fileserver.js]]): {{{ #!javascript // Express einbinden var express = require("express"); // Express-App erzeugen var app = express(); // statischen Fileserver für das Verzeichnis /app einrichten // In diesem Verzeichnis werden dann alle Dateien abgelegt, die // zur Webapp gehören, die auf dem Client läuft // - html // - js // - css // - usw. // __dirname ist das Verzeichnis des aktuell laufenden Skripts app.use(express.static(__dirname + '/app')); }}} Alle Dateien, die im Verzeichnis `/app` (relativ zum aktuell laufenden Serverskript) liegen, wird Express nun statisch mit korrektem !ContentType an den Client ausliefern. Das Verzeichnis kann auch einen beliebigen anderen Namen tragen (z. B. `public` oder `webapp` usw.). Bei der Pfadangabe ist zu beachten, dass das aktuelle Verzeichnis von der verwendeten Node.js-Instanz abhängig sein kann. Daher sollte die Pfadangabe explizit gemacht werden. Die erfolgt durch Voranstellen des absoluten Pfades zum aktuellen Skript. Der absolute Pfad wird unter Node.js in der Variablen `__dirname` bereitgestellt: {{{ #!javascript // __dirname ist das Verzeichnis des aktuell laufenden Skripts app.use(express.static(__dirname + '/app')); }}} == Eventhandler für HTTP-Methoden == Das `app`-Objekt verfügt über [[http://expressjs.com/de/4x/api.html#app.METHOD|Routing-Funktionen]], die den HTTP-Methoden entsprechen und zur Definition von Eventhandlern für eintreffende Requests fungieren: {{{ GET: app.get(pfad, eventhandler) POST: app.post(pfad, eventhandler) PUT: app.put(pfad, eventhandler) DELETE: app.delete(pfad, eventhandler) usw. }}} Beispiel für einen GET-Handler, der für alle GET-Requests die Zeichenkette "Hello World" zurückliefert. {{{ #!CodeExample ## title = GET-Handler mit Express ## repo = vorlesung ## path = code/VL10-rest_express/hello1.js #!javascript }}} == Routen == Im obigen Beispiel wird als Pfadangabe "*" verwendet. Die Pfadangabe ist ein regulärer Ausdruck, durch den die Pfade, auf die der jeweilige Eventhandler reagieren soll, gefiltert werden. Mit der Differenzierung nach Pfaden ist es demnach möglich, verschiedene Eventhandler für dieselbe HTTP-Methode (z. B. GET) zu definieren, die unterschiedliche Routen bedienen: {{{ #!CodeExample ## title = Differenzierung nach Pfaden ## repo = vorlesung ## path = code/VL10-rest_express/hello2.js ## regex = "// Handler für GET-Requests" ## lines = 17 #!javascript }}} Im obigen Beispiel würde der Pfadfilter "*" des zweiten Eventhandlers auch alle Requests mit dem Pfad "/test" akzeptieren und dann den Text "Das ist die Wildcard-Route: *" zurückliefern. Dies geschieht in diesem Beispiel nicht, da Express stets den ersten Eventhandler aufruft, der auf den aktuellen Request passt. Daher müssen Eventhandler mit speziellen Pfadangaben (hier: "/test") im Quellcode vor Eventhandlern mit generellen Pfadangaben (hier: "*") notiert werden, wie es im Beispiel oben erfolgt. == Automatische Parameterextraktion == Die Pfadangaben der Express-Eventhandler gestatten es, Platzhalter für Parameter (z. B. IDs) zu vereinbaren, die dann als Werte im Eventhandler zur Verfügung stehen. Die entsprechenden Werte werden in der Pfadangabe mit einem Doppelpunkt-Präfix gekennzeichnet. Der Parameter wird durch Express als Attribut in das Objekt `params` des Requests eingefügt: {{{ #!javascript app.get("/article/:id", function(req, res) { // ... // Zugriff auf den id-Parameter über das Objekt req.params var id = req.params.id; // ... }); }}} Clientseitig wird der Parameter einfach in den URL eingesetzt, z. B.: {{{ GET http://localhost:3000/article/42 }}} belegt die Variable `req.params.id` mit dem Wert 42. Es können mehrere Parameter an beliebigen Stellen der Pfadangabe vereinbart werden. Sie werden alle über das Objekt `req.params` unter dem jeweiligen Namen verfügbar: {{{ #!javascript // Hinzufügen einer Prüfung zu einem Studenten app.post("/student/:matrikel/exam/:courseid", function(req, res) { // ... // Zugriff auf die id-Parameter von Student und Kurs var matrikel = req.params.matrikel; var courseID = req.params.courseid; // ... }); }}} '''Achtung:''' Da es sich bei den Parametern um Bestandteile des URLs handelt, werden sie als Zeichenketten behandelt. Sofern es sich um Zahlen handelt und deren Zahleneigenschaft genutzt werden soll, ist die explizite Umwandlung in Zahlen erforderlich: {{{ #!javascript app.get("/bucket/:capacity", function(req, res) { // ... // explizite Umwandlung in eine Zahl // hier ohne Fehlerbehandlung var capacity = Number(req.params.capacity); // ... }); }}} == Express, body-parser und Objekte == Im Zusammenspiel mit dem Node.js Modul [[https://github.com/expressjs/body-parser|body-parser]] kann der technische Umgang mit JSON in REST-Interfaces weitestgehend von der Fachlichkeit separiert werden. Bevor die eigentlichen Eventhandler aufgerufen werden, parst das Modul den Inhalt des Request-Body und stellt ihn in aufbereiteter Form in dem Objekt `body` zur Verfügung, das als Attribut in das Request-Objekt eingebunden wird. Wenn es sich bei dem Inhalt des Request-Bodys um JSON oder einen Querystring (Formulardaten) handelt, ist das Objekt `body` genau das !JavaScript-Objekt, das vom Client abgeschickt wurde. Die Verwendung von `JSON.parse()` erübrigt sich also. Neben JSON und Querystring kann body-parser noch eine Reihe weiterer Inhaltstypen [[https://www.npmjs.com/package/body-parser|automatisch parsen]]. Das Modul kann mit {{{ > npm install body-parser }}} installiert werden. Die Einbindung des Moduls und die Registrierung bei Express erfolgt mit {{{ #!javascript // Express einbinden var express = require("express"); // Express-App erzeugen und konfigurieren var app = express(); // Body-Parser für Requests einbinden var bodyParser = require("body-parser"); // Parsen von Querystrings (application/x-www-form-urlencoded) aktivieren app.use(bodyParser.urlencoded({ extended: true })); // Parsen von JSON (application/json) aktivieren app.use(bodyParser.json()); }}} Nunmehr kann direkt auf das `body` Objekt zugegriffen werden (vollständiges Beispiel in [[source:/vorlesung/code/VL10-rest_express/shop/shopserver.js|shopserver.js]]): {{{ #!javascript // POST-Request: Neuen Artikel anlegen app.post("/article", function(req, res){ // req.body ist ein JavaScript-Objekt mit den Formulardaten // ID einfügen // Achtung: Unter Umständen kann es gefährlich sein, das body-Objekt strukturell zu verändern. // Insbesondere können so später unerwartete Seiteneffekte entstehen. // Dann empfiehlt es sich, zuvor eine Kopie anzulegen und diese zu verändern. // In diesem Beispiel ist das unkritisch, da das Attribut id bereits vom Client // mitgeschickt wurde (Wert -1) req.body.id = nextID; nextID++; articles.push(req.body); updateFile(); res.contentType("application/json"); res.status(201).send(JSON.stringify(req.body)); }); }}} Von besonderer Bedeutung bei der Verwendung von body-parser ist, dass der !ContentType korrekt gesetzt ist. Im Zusammenspiel mit jQuery erfolgt dies clientseitig oft automatisch. Im folgenden Beispiel wird der Inhalt eines HTML-Formulars automatisch als Querystring mit dem !ContentType `application/x-www-form-urlencoded` verschickt. HTML-Formular: {{{ #!htm ...
Neu / Bearbeiten



... }}} Definition des Eventhandlers für den Versand des Formulars unter Verwendung der [[http://api.jquery.com/serialize/|serialize]]-Funktion von jQuery: {{{ #!javascript $(document).ready(function () { // Eventhandler für den Submit-Button des Formulars $("#newentry").submit(function(e) { $.ajax({ type: 'POST', url: '/article', // Daten des Formulars als Querystring serialisieren data: $("#newentry").serialize(), // Wird bei Eintreffen der Antwort aufgerufen success: function(data) { // Ansicht aktualisieren } }); // Verhindern, dass der normale Submit des Browser ausgeführt wird e.preventDefault(); }); }); }}} Die jQuery-Funktion `serialize` erstellt aus allen in einem Formular enthaltenen Feldern einen Querystring. Für Objekte mit der Struktur {{{ #!javascript { name: "Wasserwaage", preis: 23.99, beschreibung: "...", id: 4 } }}} würde der Querystring wie folgt aussehen: {{{ name=Wasserwaage&preis=23.99&beschreibung=...&id=4 }}} wobei die Schlüssel die `name`-Attribute der Formularfelder und die Werte die in den Formularfeldern enthaltenen Werte sind. Voraussetzung für das reibungslose Zusammenspiel von Client und Server ist das konsequent einheitliche Benennen von Attributen und Formularfeldern. Die Namen (Attribut `name`) der Eingabeelemente des HTML-Formulars müssen den Namen der Attribute der übertragenen Objekte entsprechen. Wenn man diese Konvention einhält, kann das im Beispiel implementierte REST-Interface für ein Shopsystem nahezu ohne Änderung im Quellcode für vollkommen andere Objekte verwendet werden. Anzupassen wären nur die Pfade der Eventhandler auf dem Server, die den Objekttyp kennzeichnen, und die `name`-Attribute der Formularelemente auf dem Client. == Beispiel REST-Interface mit Express == In der Datei [[source:vorlesung/code/VL10-rest_express/shop/shopserver.js|shopserver.js]] findet sich eine Express-basierte Implementierung des im vorigen Kapitel vorgestellten REST-Interfaces, wobei die URLs unverändert sind. Der zugehörige HTML/JS-Client befindet sich im Verzeichnis [[source:vorlesung/code/VL10-rest_express/shop/app|app]].