Skip to content
On this page

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.

Recommended

Recommended

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:

started

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.

Request 1

Server Seite

GET Request implementieren

  1. Erstellen Sie nun einen neuen GET Endpoint an /api/commands, welcher ein Array von Commands in folgender Form zurückgibt:
    json
    [
     {
         "name": "ls",
         "desc": "lists files"
     },
     {
         ...
     }
    ]
    
    Erstellen Sie hierzu eine globale Variable const commands = [], welche die Commands während der Laufzeit des Servers im Memory hält.
  2. 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)
});
  1. Implementieren Sie einen POST Endpoint /api/add-command, der im Body einen Command erwartet (gleiche Struktur wie oben).
  2. Fügen Sie den Command dem Array hinzu.
  3. Bestätigen Sie den Request in dem Sie als Response { status: 'ok' } zurücksenden.
  4. Verwenden Sie Thunder Client um eine JSON Payload via POST Request zu senden.
  5. Ü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");
  1. Speichern Sie jeweils nach jedem POST den aktuellen Inhalt des Arrays. Wie können Sie einen JSON in einen String umwandeln?
  2. Laden Sie das File commands.json nun jeweils beim Neustart ins Memory. Verwenden Sie dazu die analoge Methode fs.readFileSync.
  3. 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.

Hier der Link zur Live-Demo.

Schauen Sie sich den Source der Live Demo an, falls Sie keine Version des Versuchs bereit haben.

  1. Erweitern Sie den Code, in dem Sie als erstes einen GET Request bei onload an den Server senden um alle Commands abzuholen.
  2. 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 error

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.

Fixed

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.

  1. 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.

    js
    const nodes = document.querySelectorAll("#main-container > div");
    nodes.forEach((node) => {
        // itterate over nodes
    });
    
  2. Im onclick event des Buttons können Sie nun einen neuen POST Request ausführen. Sie können sich an folgendes Beispiel lehnen:
    js
    const 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).

Adding / Listing

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.