Version 3 (modified by tr, 6 years ago) (diff) |
---|
Express
Literaturhinweis
Roden: Node.js & Co. - Kapitel 10
Einführung
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:
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
- MongoDB,
- Express,
- AngularJS und
- Node.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 hello_fileserver.js):
// 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:
// __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 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.
// HTTP Modul einbinden var http = require('http'); // Express einbinden var express = require('express'); // Express-App erzeugen var app = express(); // HTTP Server erzeugen var server = http.createServer(app); // Server an Port binden server.listen(3000); // Handler für GET-Requests // * ist die Route und kennzeichnet hier, dass GET-Requests für // alle URLs (Routen) angenommen werden app.get('*', function(req, res) { res.contentType('text/html'); res.status(200).send('Hello World'); });
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:
// Handler für GET-Requests // test ist die Route und kennzeichnet hier, dass GET-Requests für // den URL /test angenommen werden. // Als Filter für Routen können reguläre Ausdrücke verwendet werden. // Falls mehrere Routen für denselben URL formuliert sind, wird die in der // .js-Datei zuerst aufgeführte verwendet. app.get('/test', function(req, res) { res.contentType('text/html'); res.status(200).send('Das ist die Route /test'); }); // * ist die Route und kennzeichnet hier, dass GET-Requests für // alle sonstigen URLs (Routen) angenommen werden. app.get('*', function(req, res) { res.contentType('text/html'); res.status(200).send('Das ist die Wildcard-Route: *'); });
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:
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:
// 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:
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 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 automatisch parsen.
Das Modul kann mit
> npm install body-parser
installiert werden.
Die Einbindung des Moduls und die Registrierung bei Express erfolgt mit
// 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 shop.js):
// 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:
... <form id="newentry" name="newentry" method="post" action="/article"> Neu / Bearbeiten<br /> <label for="name">Name</label><input id="name" name="name" type="text" /><br /> <label for="preis">Preis</label><input id="preis" name="preis" type="text" /><br /> <label for="beschreibung">Beschreibung</label><input id="beschreibung" name="beschreibung" type="text" /><br /> <input id="id" name="id" type="hidden" value="-1" /> <button id="neu" type="submit">Absenden</button> <input id="clear" type="button" value="Formular leeren" /> </form> ...
Definition des Eventhandlers für den Versand des Formulars unter Verwendung der serialize-Funktion von jQuery:
$(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
{ 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 shop.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 app.