© Shutterstock.com/Muhammad ZA, Shutterstock.com/hudasaktian
Gestatten, Node.js – Teil 2

Web-APIs mit Node entwickeln


Eines der häufigsten Einsatzgebiete von Node.js ist das Entwickeln von Web-APIs. Dafür stehen zahlreiche Module aus der Community zur Verfügung, die eine ganze Reihe von Aspekten abdecken, beispielsweise Routing, Validierung und CORS.

Der erste Teil dieser Serie hat Node.js als serverseitige Laufzeitumgebung für JavaScript vorgestellt und gezeigt, wie man einen einfachen Webserver schreibt. Außerdem wurde die Paketverwaltung npm vorgestellt, mit der sich von der Community geschriebene Module leicht im Kontext der eigenen Anwendung installieren lassen. Auf der einen Seite sind also bereits einige Grundlagen bekannt, auf der anderen Seite fehlt es der bislang entwickelten Anwendung noch an sinnvoller Funktionalität.

Genau das wird sich in diesem Teil ändern: Die Anwendung, die bisher lediglich einen rudimentären Webserver startet, soll ein API zur Verfügung stellen, mit dem man eine Aufgabenliste verwalten kann. Dazu gilt es zunächst, einige fachliche Vorüberlegungen anzustellen, denn es muss definiert werden, was genau die Anwendung überhaupt leisten soll. Beispielsweise wären die folgenden Funktionen denkbar:

  • Es muss möglich sein, eine neue Aufgabe zu notieren. Eine solche besteht in der einfachsten Form lediglich aus einem Titel, der allerdings nicht leer sein darf.

  • Außerdem muss sich die Liste aller noch zu erledigenden Aufgaben abrufen lassen, damit man nachvollziehen kann, was noch zu tun ist.

  • Zu guter Letzt muss es möglich sein, eine erledigte Aufgabe abzuhaken, so dass sie aus der Liste der noch zu erledigenden Aufgaben entfernt wird.

Diese drei Funktionen sind essenziell, ohne sie kann eine Aufgabenliste nicht sinnvoll genutzt werden. Alle weiteren Funktionen, beispielsweise das Umbenennen einer Aufgabe oder das Widerrufen des Abhakens einer Aufgabe, sind hingegen optional. Natürlich wäre es im Sinne einer möglichst benutzerfreundlichen und komfortablen Anwendung durchaus sinnvoll, sie zu implementieren – wirklich notwendig sind sie aber nicht. Die drei zuvor genannten Funktionen stellen sozusagen den Umfang eines Minimum Viable Products (MVP) dar.

Eine weitere Einschränkung soll ebenfalls direkt zu Beginn festgelegt werden: Die Aufgabenliste soll bewusst nicht über eine Benutzerverwaltung verfügen, um das Beispiel überschaubar zu halten. Das bedeutet, dass es weder eine Authentifizierung noch eine Autorisierung gibt, und dass es nicht möglich sein wird, mehrere Aufgabenlisten für verschiedene Personen zu verwalten. Auch das wäre für eine produktive Nutzung der Anwendung unerlässlich, doch würde es den Rahmen dieses Artikels sprengen und letztlich nur wenig Lerneffekt für Node.js bieten.

Der aktuelle Stand

Der aktuelle Stand der Anwendung, die wir im ersten Teil geschrieben haben, umfasst zwei Codedateien: app. js, die den eigentlichen Server startet, und lib/getApp.js, die die Funktionalität enthält, mit der auf Anfragen von außen reagiert wird. In der Datei app.js wurde bereits das npm-Modul processenv [1] genutzt, um den Port über eine Umgebungsvariable auf einen anderen Wert als den Standardwert 3000 setzen zu können (Listing 1).

Listing 1

'use strict'; const getApp = require('./lib/getApp'); const http = require('http'); const { processenv } = require('processenv'); const port = processenv('PORT', 3000); const server = http.createServer(getApp()); server.listen(port);

Die gute Nachricht an dieser Stelle lautet, dass sich an dieser Datei nichts ändern wird. Das liegt daran, dass in den Dateien app.js und getApp.js bereits eine inhaltliche Trennung vorliegt: Die erste Datei kümmert sich um den HTTP-Server an sich, die zweite enthält die eigentliche Logik der Anwendung. Da in diesem Teil der Artikelserie lediglich die Anwendungslogik angepasst und erweitert wird, kann die Datei app.js bleiben wie sie ist.

Anders sieht es hingegen in der Datei getApp.js aus, bei der kaum ein Stein auf dem anderen bleibt. Doch eins nach dem anderen. Zuvor gilt es noch, die Datei package.json so anzupassen, dass der Name der Anwendung etwas aussagekräftiger wird. Es bietet sich beispielsweise an, die Anwendung statt my-http-server zukünftig aufgabenliste zu nennen:

{ "name": "aufgabenliste", "version": "0.0.1", "dependencies": { "processenv": "3.0.2" } }

Die Datei- und Verzeichnisstruktur der Anwendung sieht damit nach wie vor so aus wie im ersten Teil:

/ lib/ getApp.js node_modules/ app.js package.json package-lock.json

REST? Nein, danke!

Nun gilt es, das Routing einzubauen. Das erfolgt, wie bei APIs üblich, über verschiedene Pfade in den URLs des API, außerdem kann man auf die verschiedenen HTTP-Verben wie GET und POST zurückgreifen, um unterschiedliche Aktionen abzubilden. Ein gängiges Pattern ist dabei der sogenannte REST-Ansatz, der definiert, dass über den URL sogenannte Ressourcen definiert werden und die HTTP-Verben die Aktionen auf diesen Ressourcen definieren. Die übliche Abbildung gemäß REST lautet dabei wie folgt:

  • POST legt eine Ressource neu an, entspricht also einem Create.

  • GET ruft eine Ressource ab, stellt also das klassische Read dar.

  • PUT aktualisiert eine Ressource, entspricht also einem Update.

  • DELETE schließlich löscht eine Ressource, entspricht also einem Delete.

Wie man sieht, kann man diese vier HTTP-Verben sehr einfach auf die vier Aktionen des sogenannten CRUD-Patterns abbilden, das wiederum dem gängigen Ansatz entspricht, wie man auf Daten in (relationalen) Datenbanken zugreift. Das ist nicht zuletzt einer der wichtigsten Gründe für den Erfolg von REST: Es ist einfach und baut auf der bereits vertrauten Logik von Datenbanken auf. Trotzdem gibt es einige Gründe, die gegen den Einsatz dieser Übertragung von CRUD auf die API-Ebene sprechen. Am gewichtigsten dabei ist, dass die Verben nicht der Fachsprache entsprechen: Anwenderinnen und Anwender sprechen nicht davon, eine Aufgabe zu createn oder zu updaten.

Stattdessen denken sie in fachlichen Vorgängen: Sie möchten sich eine Aufgabe notieren oder eine Aufgabe als erledigt abhaken. Hier prallen also eine fachliche und eine technische Sicht aufeinander. Dass an irgendeiner Stelle ein Mapping zwischen diesen Sichten stattfinden muss, liegt auf der Hand – doch sollte der Code einer Anwendung tendenziell eher fachlich als technisch strukturiert [2] sein. Schließlich wird die Anwendung auch zur Lösung eines fachlichen Problems geschrieben, und Technologie stellt hier lediglich das Mittel zum Zweck dar. So gesehen ist auch CRUD ein Antipattern [3].

Einen alternativen Ansatz liefert das CQRS-Pattern, das auf Commands und Queries [4] basiert. Ein Command ist dabei eine Aktion, die den Zustand der Anwendung verändert,...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang