Appearance
HTTP Server mit Node und ExpressJS
Ziel in diesem Versuch ist es, einen eigenen Server auf dem Gastsystem zu implementieren und via REST Client (z.B. Thunder Client) zuzugreifen. In einem zweiten Schritt erstellen Sie eine clientseitige Applikation mit HTML, JS und CSS auf die API zugreift.
Folgende Ziele werden mit diesem Versuch erreicht:
- Eigenen Webserver mit expressjs erstellen
- GET und POST Requests implementieren
- Web Server mit Thunder Client und
curl
testen - Client App erstellen, welche die API verwendet
Als Beispiel werden wir eine Linux "Commands" Bibliothek erstellen, bei der Sie Linux Commands (Name + Beschreibung) erstellen und löschen können.
Setup
Clonen Sie folgendes Template in Ihr ~/web
Verzeichnis: https://gitlab.fhnw.ch/wlw/template-server.git
. Benennen Sie das Verzeichnis um nach linux-commands
.
Öffnen Sie das Projekt in VS Code.
Es wird Ihnen vorgeschlagen einige Extensions zu installieren.
Installieren Sie die Extensions, sie dienen hauptsächlich dazu, dass der Code richtig formattiert wird und der Source Code auf Fehler überprüft werden kann (wie Sie bereits wissen wird JavaScript nicht kompiliert und hat deshalb standardmässig nur Detektion von Fehlern zur Laufzeit).
Öffnen Sie ein Terminal mit CTRL + J
und führen Sie yarn
aus um alle dependencies zu installieren. Den Server implementieren wir in src/index.js
. Wir können den Server bereits starten mit yarn start
.
package.json
und nodemon
Das Template hat für Sie bereits ein paar Einstellungen vorgenommen (vor allem Code Formatting). Auch finden Sie in package.json
ein erstes script
, dass nodemon src/index.js
ausführt. In Production würden Sie den nodemon
Command einfach durch node
ersetzen.
nodemon
ist ein Tool, dass erkennt, wenn Sie ein File speichern und dannach das Script automatisch neustartet. Das heisst, mit diesem Hot Reload müssen Sie nach dem speichern von index.js
nicht jedes mal den Webserver neustarten.
Sobald Sie den Server gestartet haben, sollten Sie folgenden Output sehen:
API verwenden
Der Webserver den wir als Template verwenden ist sehr einfach. Er gibt bei einem GET
auf /
einfach hello
aus. Die Applikation läuft auf Port 3000
, wie im Code konfiguriert.
js
import express from 'express';
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('hello');
});
app.listen(port, () => {
console.log(`Linux Commands available on ${port}`);
});
Öffnen Sie nun Thunder Client und führen Sie einen Request zum Endpoint aus.
Server Seite
GET Request implementieren
- Erstellen Sie nun einen neuen GET Endpoint an
/api/commands
, welcher ein Array von Commands in folgender Form zurückgibt:jsonErstellen Sie hierzu eine globale Variable[ { "name": "ls", "desc": "lists files" }, { ... } ]
const commands = []
, welche die Commands während der Laufzeit des Servers im Memory hält. - Testen Sie den neuen GET Endpoint mit Thunder Client. Testen Sie auch mit
curl
.
POST Request handeln
Auch möchten wir neue Commands erstellen können. Hierzu erstellen wir einen POST
Endpoint, welcher den body
des Requests auf einen neuen Command überprüft und diesen dem Array commands
hinzufügt.
Die Payload im POST
wird im JSON Format sein. Fügen Sie folgende "middleware" der app
hinzu, welche dafür zuständig ist, den Body als JSON
zu parsen.
js
app.use(express.json());
Einen POST endpoint erstellen Sie folgendermassen:
js
app.post('/somepath', (req, res) => {
const body = req.body;
console.log('body', body)
});
- Implementieren Sie einen POST Endpoint
/api/add-command
, der im Body einen Command erwartet (gleiche Struktur wie oben). - Fügen Sie den Command dem Array hinzu.
- Bestätigen Sie den Request in dem Sie als Response
{ status: 'ok' }
zurücksenden. - Verwenden Sie Thunder Client um eine JSON Payload via POST Request zu senden.
- Überprüfen Sie mit GET ob der Command erfolgreich der Liste hinzugefügt worden ist.
Daten Persistieren (poor mans db)
Mit folgendem Command können Sie ein File schreiben und speichern:
js
import fs from "fs"
// ...
fs.writeFileSync('./commands.json', "sometext");
- Speichern Sie jeweils nach jedem
POST
den aktuellen Inhalt des Arrays. Wie können Sie einen JSON in einen String umwandeln? - Laden Sie das File
commands.json
nun jeweils beim Neustart ins Memory. Verwenden Sie dazu die analoge Methodefs.readFileSync
. - Testen Sie die neue funktionalität mit Thunder Client.
Gratulation, Sie haben soeben einen volständigen Webservice mit Datenbankanbindung geschrieben!
Commands löschen
Erlauben Sie nun wie POST
an /api/rm-command
einen Command aus dem Array zu löschen und persistieren Sie wieder. Der Command, welcher gelöscht werden soll wird als JSON Body im POST Request übergeben.
Testen Sie auch hier wieder mit Thunder Client.
Tipp
Verwenden Sie die .filter()
Methode des Arrays um eine Version ohne den zu entfernenden Command zu erhalten.
Client App
Verwenden Sie für den Client die Appliaktion, welche wir bereits in Versuch JS DOM
erstellt haben.
Schauen Sie sich den Source der Live Demo an, falls Sie keine Version des Versuchs bereit haben.
- Erweitern Sie den Code, in dem Sie als erstes einen
GET
Request bei onload an den Server senden um alle Commands abzuholen. - Sie werden feststellen, dass ein Fehler beim Request passiert. Schauen Sie hierbei in die JS Console in den Debug Tools des Browsers. Was ist schiefgelaufen?
CORS auf dem Server erweitern
CORS
ist der Sicherheitsmechanismus, der es verhindern soll, dass der Server Opfer eines DDoS wird. Wir werden dies im Moment elegant umgehen, in dem wir bei jeder Response im Header Access-Control-Allow-Origin: *
mitgeben.
Hierzu erstellen wir Serverseitig eine "Middleware", welche bei jedem Request den Header injected.
js
app.use((_, res, next) => {
res.setHeader('access-control-allow-origin', '*');
res.setHeader('access-control-allow-headers',
'Origin, X-Requested-With, Content-Type, Accept');
return next();
});
Natürlich gilt es für eine App in Produktion abzuklären, ob diese CORS Regel Sinn macht. Ist der Dienst im Internet verfügbar empfiehlt es sich hier restriktivere Regeln zu setzen. Das Node Packet cors
hat hierfür ein eigenes Framework.
Geht der Request einmal durch, sollte der Fehler im Client nun verschwinden.
Neue Einträge an den Server senden
Bisher haben wir neue Einträge nur Clientseitig in den DOM eingefügt. Neu wollen wir die neuen Einträge an den Server senden und dann alle Einträge nochmals vom Server holen (als Bestätigung, dass es geklappt hat).
Sobald wir die Antwort vom Server erhalten, möchten wir die Clientseitigen Elemente alle löschen und die des Servers hinzufügen.
- Erstellen Sie zuerst eine neue Methode
refresh
, welche im Client alle DOM Commands Elemente entfernt, vom Server alle Commands holt und den DOM wieder aufbaut. Testen Sie ob es klappt.Alle Child Nodes Selektieren
Mit
document.querySelectorAll
können gleich mehrere Nodes selketiert werden. So können Sie z.B. mit#main-container > div
alle direkten Childnodes von#main-container
selektiert werden.jsconst nodes = document.querySelectorAll("#main-container > div"); nodes.forEach((node) => { // itterate over nodes });
- Im
onclick
event des Buttons können Sie nun einen neuen POST Request ausführen. Sie können sich an folgendes Beispiel lehnen:jsconst response = await fetch('<url>', { method: 'POST', headers: { 'Content-Type' : 'application/json' }, body: JSON.stringify(payloadHere) }); const json = await response.json();
Verifizieren Sie, dass der POST gesendet wird und dass bei refresh()
die Einträge neu geladen werden mit Hilfe der Browser Debug Tools (Network Tab).
Einträge mit Click entfernen
Verfahren Sie für das entfernen eines Commands analog wie beim hinzufügen. Der POST Endpoint existiert ja bereits, Sie müssen diesen nur noch im Client einbinden.