wiki:skript/express

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.