Node.JS
Datei ausführen:
node <dateiname>
Öffne Node.js REPL (read eval print loop):
node
können hier JS-Code eingeben, der direkt ausgeführt wird, ähnlich wie in Entwicklerkonsolen der Webbrowser
REPL verlassen durch Eingabe von .exit
Hint
Mit require(…) importiert Node.js Module.
Gleichzeitige Ausführung
Node.js versucht jede Anweisung so schnell wie möglich auszuführen. Programm enthält aber auch viele Anweisungen, die langsam sind (z.B. Einlesen einer Datei). Bei synchronen Methoden laufen im Hintergrund Anweisungen, die warten müssen
d.h. Anweisung blockiert weitere Ausführung des Programms
Programm läuft nicht 100% effizient, wenn es dauernd warten muss
und wenn noch weitere Dateien eingelesen werden sollen…
Um möglichst effizient zu arbeiten, sollte Programm sofort damit beginnen. In der Informatik zwei gibt Ansätze, die Problem lösen:
asynchrone Programmierung und
Multithreading
JavaScript bevorzugt asynchrone Programmierung
Dabei wird einer Anweisung Code mitgegeben, der ausgeführt werden soll, sobald Anweisung das gewünschte Ergebnis erhalten hat. Mitgegebener Code wird also asynchron »irgendwann später« ausgeführt JavaScript-Programm läuft immer in einem einzelnen Thread ab. Callbacks kommen in Reihenfolge ihres Auftretens in Warteschlange - werden erst ausgeführt, wenn nichts weiteres zu tun ist.
Multithreading
Alternative zu asynchroner Programmierung nutzt Tatsache, dass moderne Betriebssysteme mehrere Dinge gleichzeitig machen können (Multitasking). Programm startet einfach weiteren »Neben-Task«, der sich im Hintergrund um möglicherweise langsame, blockierende Anweisung kümmert. Technisch wird dieser »Neben-Task« meist als sogenannter Thread implementiert – man spricht von Multithreading. Die Kunst besteht darin, Threads korrekt zu koordinieren wenn mehrere Threads dieselben Variablen gleichzeitig (oder zeitlich verschränkt) unkoordiniert verändern, führt das zu Fehlern, die sehr schwer zu finden sind. Selbst dort, wo Einlesen der Datei unabhängig von Berechnung ist, könnte Lösung mit Multithreading-Ansatz problematisch sein. Reihenfolge der Ausgabe könnte sich willkürlich ändern, je nachdem, ob Haupt-Thread oder Neben-Thread schneller war Programm reagiert nicht mehr vorhersehbar und kann nicht zuverlässig getestet werden. Asynchroner Ansatz in dieser Hinsicht robuster!
Node-Standardbibliothek
Bibliothek definiert gut dokumentierte Schnittstelle (engl. interface) mit Angaben zu Funktionsaufrufen. So eine Schnittstelle wird häufig API (engl. application programming interface) genannt.
Standard-Module (Auswahl):
Modul |
Zweck |
|---|---|
buffer |
Buffer |
console |
Ausgabe/Debugging in die Eingabeaufforderung (z.B.console.log(…)) |
crypto |
Kryptographie |
fs |
Dateisystemzugriff |
os |
Interaktion mit dem Betriebssystem |
stream |
Datenströme |
zlib |
Datenkomprimierung |
jedes Modul hat Stabilitätswert:
beschreibt Stabilität der Schnittstelle
Bereich geht von 0 (sehr instabil) zu 3 (sehr stabil)
Module mit Werten 2-3 ruhig verwenden ohne Angst zu haben, dass zukünftige Version der Standardbibliothek die Schnittstelle so ändert, dass Code nicht mehr funktioniert.
Buffer = beliebige Folge von Bytes Unterschied zu Strings: Strings enthalten druckbare Bytes (also Zeichen), die bestimmtem Encoding folgen (bsw. UTF-8) Buffer enthalten beliebige Daten: gzip-komprimierte Daten, Bilder, Messdaten eines Sensors oder …
Stream-API ist sehr komfortabel
pipe(…)-Funktion erlaubt es, Streams zu verbinden (daher der Name »pipe«, englisch für Röhre)
Eingabestrom wird damit an Ausgabestrom angesteckt und pipe(…) kümmert sich darum, benötigte Callbacks zu erstellen
Beispiel: fs-Modul
https://nodejs.org/docs/latest/api/fs.html#file-system
Beinhaltet etwa 100 Funktionen
Name steht für Dateisystem (engl.filesystem)
Modul enthält Funktionen zum Umgang mit Dateien - bsw. Funktion fs.readFileSync(…)
fs.readFileSync(…)
liest eine Datei ein (blockierende Variante)
Beispiel: const data = fs.readFileSync(“products.csv”, “UTF8”);
fs.readFile(…)
liest eine Datei ein (asynchrone Variante)
Beispiel: const data = fs.readFile( “products.csv”, “UTF8”, (error, data) => console.log(data) );
fs.writeFileSync(…)
schreibt Daten in eine Datei (blockierende Variante)
bereits vorhandene Daten werden überschrieben
Beispiel:
fs.writeFile(…)
schreibt Daten in eine Datei (asynchrone Variante)
bereits vorhandene Daten werden überschrieben
Beispiel:
const data = "Hello World";
fs.writeFile("hello.txt", data, "UTF8", error => { if (error) console.log("Error: " + error); } );
fs.statSync(…)
ermittelt Infos zur Datei (blockierende Variante)
Beispiel:
const stats = fs.statSync("hello.txt");
console.log(stats.size); // => size in bytes
fs.stat(…)
ermittelt Infos zur Datei (asynchrone Variante)
Beispiel: fs.stat(“hello.txt”, (error, stats) => console.log(stats.size) ); // => size in bytes
fs.unlinkSync(…)
löscht eine Datei (blockierende Variante)
Beispiel: fs.unlinkSync(“hello.txt”);
fs.unlink(…)
löscht eine Datei (asynchrone Variante)
Beispiel: fs.unlink(“hello.txt”, error => { if (error) console.log(“Error: “ + error);});
Import/Export: CommonJS vs. ECMAScript Module (ES)
NodeJS verwendet standardmäßig das CommonJS Modulformat.
Bei der Verwendung von Modulen mit NodeJS ist es wichtig zu erkennen, ob es sich bei einem lokalen JavaScript-Datei um ein CommonJS Modul oder ein ECMAScript Module handelt. Diese unterscheiden sich beispielsweise in der Art, wie deren Methoden exportiert und importiert werden.
Important
Die folgenden Beispiele beschränken sich auf lokale Module, welche nicht
per npm installiert wurden. Hierfür die
Beschreibung unten lesen.
// CommonJS (nur default export)
// ========
// module.js
// ---------
// Definition
const doubleVal = (val) => {
return val * 2;
};
// Export
module.exports = { doubleVal };
// init.js
// -------
// 1) Named Import
const { doubleVal } = require("./module.js");
// Verwendung
console.log(doubleVal(2)); // 4
// 2) Default Import
const module = require("./module.js")
// Verwendung
console.log(module.doubleVal(2)) // 4
Important
Da NodeJS standardmäßig mit CommonJS Module arbeitet, muss für die Einbindung von ECMAScript Module entweder
Module mit dem Suffix
.mjsenden (statt.js) oderin
package.jsonder Typ aufmodulegeändert werden (CommonJS:commonjs):"type": "module"
Wichtig: Für den Named Export / Import muss "type": "module" gesetzt sein!
// ES Module - Default Export
// ==========================
// module.mjs
// ----------
// Definition
const doubleVal = (val) => {
return val * 2;
};
// Export
export { doubleVal }
// init.js
// -------
// Import (nur default import möglich)
import module from "./module.js";
// Verwendung
console.log(module.doubleVal(2)); // 4
// ES Module - Named Export (benötigt "type": "module")
// ====================================================
// module.mjs
// ----------
// Definition + Export
export const doubleVal = (val) => {
return val * 2;
};
// init.js
// -------
// 1) Named import
import { doubleVal } from "./module.js";
// Verwendung
console.log(doubleVal(2)); // 4
// 2) Default import
const myModule = require("./myModule.js")
// Verwendung
console.log(myModule.doubleVal(2)) // 4
Standardmodule einbinden
Auch hier ist der Typ des Projekts entscheidend. Standardmodule können über das
require statement in CommonJS Module importiert werden. Bei ECMAScript Modulen
hingegen werden diese über das import statement importiert.
CommonJS Module (mit require):
const fs = require("fs");
// importiert fs-Standartmodul aus nodeJS Bibliothek
// fs stellt uns Methoden zur Arbeit mit FileSystem zur Verfügung
fs.writeFile("hello.txt", data, "UTF8", error => { if (error) console.log("Error: " + error); } );
ECMAScript Module ("type": "module" - mit import):
// Import
import fs from "fs";
// importiert fs-Standartmodul aus nodeJS Bibliothek
// fs stellt uns Methoden zur Arbeit mit FileSystem zur Verfügung
// Verwendung
const data = "Hello World";
fs.writeFile("hello.txt", data, "UTF8", error => { if (error) console.log("Error: " + error); } );
npm
Sammlung von Modulen für node.js auf http://www.npmjs.com. Die meisten Module sind unter freizügigen Open Source-Lizenz verfügbar - dürfen diese Module somit uneingeschränkt einsetzen.
Validator-Modul (https://www.npmjs.com/package/validator) kann Daten aller Art validieren (Zahlen in verschiedenen Formaten, komplexere Daten, wie Datumsangaben, Kreditkartennummern oder ISBNs).
validator-Modul mit npm installieren:
npm install validator@12
Weist npm an, neueste Version des validator-Moduls mit Hauptversion 12 zu installieren.
Ohne Angabe @12 würde npm einfach neueste Version installieren.
Meist besser an einer Hauptversion festzuhalten, da sich Teile eines Moduls in
zukünftigen Hauptversionen ändern können - evtl. gibt es bestimmte Modulfunktion
in Folgeversion nicht mehr.
Node.JS Module müssen mit Semantic Versioning benannt sein (https://semver.org/).
Module für eigene Projekte
Installierte Module sollten als Abhängigkeit im Projekt eingebracht werden. Nicht besonders praktisch, Module vorzuinstallieren und zusammen mit Software zu veröffentlichen wäre z.B. mühsam, Module aktuell zu halten. außerdem nicht garantiert, dass npm beim Installieren nicht gewisse Schritte ausführt, die vom Betriebssystem abhängen. besser, dem Programm Anweisungen beizulegen, welche Module(in welchen Versionen) benötigt werden, um Programm zum Laufen zu bringen.
Abhängigkeiten in package.json Datei listen:
Wenn package.json im Verzeichnis liegt und im selben Verzeichnis Befehl npm install ausgeführt wird, installiert npm automatisch abhängige Module (in den richtigen Versionen).
Anwendungsmöglichkeiten des npm-Befehls
|
installiere Modul in der Version |
|
|
entferne Modul in der Version |
|
|
update Modul auf die neueste Version |
|
|
erstelle package.json-Datei (Befehl fragt Eigenschaften ab) |
|
|
erstellt eine package.json-Datei (Befehl setzt Default-Werte) |
|
|
listet alle installierten Module auf |
|
|
suche Module |
deren Name Suchbegriff enthält |
Hint
npm install, npm uninstall und npm update kennen Option --global
(oder kurz: -g). Mit dieser Option handeln Befehle im Installationsverzeichnis
von Node.js selbst - Module werden global installiert, entfernt oder aktualisiert
diese Option mit Vorsicht verwenden - evtl. werden Module von anderen Programmen
noch gebraucht?
Eigene Module schreiben
Sammlung an JavaScript-Funktionen lässt sich einfach in Node.js-Modul verwandeln.
Funktionen, die dem Aufrufer des Moduls zur Verfügung gestellt werden sollen,
müssen zu vordefiniertem Objekt mit treffendem Namen export (ECMAScripts)
oder module.exports (CommonJS) hinzugefügt werden.
ECMAScript Module (Projekt package.json: "type": "module"):
// namesmodule/index.js
// --------------------
// call 'shorter' method for each element in shortNames array
const shortNames = namesArr => namesArr.map(name => shorter(name));
// convert name string, e.g. Arne Franz Wohletz -> A. F. Wohletz
const shorter = namesStr => {
let tokenArr = namesStr.trim().split(/\s+/);
let lastName = tokenArr.pop();
return tokenArr
.map(firstName => firstName.charAt(0).toUpperCase() + ". ")
.join("") + lastName;
};
// export shortNames function (allows import in other modules)
export {shortNames}
// myproject/index.js
// ------------------
// Import
import { shortNames } from "namesmodul";
// Verwendung
console.log(shortNames(["Rainer Maria Rilke"])); // [ 'R. M. Rilke' ]
CommonJS (Projekt package.json: "type": "commonjs"):
// call 'shorter' method for each element in shortNames array
const shortNames = namesArr => namesArr.map(name => shorter(name));
// convert name string, e.g. Arne Franz Wohletz -> A. F. Wohletz
const shorter = namesStr => {
let tokenArr = namesStr.trim().split(/\s+/);
let lastName = tokenArr.pop();
return tokenArr
.map(firstName => firstName.charAt(0).toUpperCase() + ". ")
.join("") + lastName;
};
// export shortNames function (allows import in other modules)
module.exports = { shortNames };
// myproject/index.js
// ------------------
// 1) Named import
import { shortNames } from "namesmodul";
console.log(shortNames(["Rainer Maria Rilke"])); // [ 'R. M. Rilke' ]
// 2) default export
import myModule from "namesmodul";
console.log(myModule.shortNames(["Rainer Maria Rilke"])); // [ 'R. M. Rilke' ]
eingebunden werden.
Schreibweise ./moduldatei.js forciert Einbinden des Moduls aus aktuellem Verzeichnis bei Zuweisung mit require(…) auch möglich, zu entscheiden, nur auf bestimmte Funktion zuzugreifen:
Vom Modul zum npm-Paket
Wenn Modul öfter verwendet oder auf npmjs.com veröffentlicht werden soll, npm-Paket erstellen.
Dazu Verzeichnis erstellen mit:
Namen des Moduls gefolgt von @-Zeichen und Versionsnummer bsw.
namesmodule@0.0.1dort hinein kommt Code des Moduls sowie eine
package.json, die Modul beschreibt
Wichtige Eigenschaft ist “main” - hier wird Einsprungpunkt definiert, also Datei mit
exports-Definitionen. Modul darf auch aus mehreren Dateien bestehen, gibt aber immer
genau einen Einsprungpunkt. Die Felder keywords, author und license
ausfüllen, wenn Modul veröffentlicht werden soll wenn Modul weitere Module benötigt,
diese über dependencies-Eigenschaft deklarieren.
Installation über
npm install pfad/zum/Modul/modul
Darauf achten, dass im Projekt eine package.json liegt, um Abhängigkeiten
automatisch hinzugefügt zu bekommen
Attention
Pfad in package.json muss angepasst werden, wenn Modul aus Ordner von
Rechner installiert wurde - relative Pfade sind nicht erlaubt!
npm muss am Argument erkennen können, dass es sich um Verzeichnis handelt.
npm geht so vor, dass es im Zweifelsfall probiert, Argument als lokales
Verzeichnis zu öffnen. Dort sucht es nach package.json — falls das nicht
klappt, wird auf http://www.npmjs.com gesucht.
Deinstallieren über: npm uninstall name_des_moduls
- Wenn wir modul im Ordner ausgeliefert bekommen reicht Dank
package.json ein
npm install, um es zu installieren und Anwendung zum Laufen zu bringen.
npm-Pakete veröffentlichen
npm macht es leicht, Pakete direkt auf npmjs.com oder eigenem Webserver zu veröffentlichen.
Dazu npm-Modul-Verzeichnis als TGZ-Datei archivieren und auf npm oder Webserver laden.
Befehl unter MacOS und Linux, um TGZ-Datei zu erstellen:
tar zcf namesmodule@0.0.1.tgz namesmodule@0.0.1
Unter Windows eines der bekannten Archivprogramme benutzen oder innerhalb des
Modul-Ordners npm pack nutzen => liest package.json aus und nutzt Name und Version.
Modul dann installieren mit:
npm i namesmodul (Hosting bei npm)
oder
npm i http://www.domain.de/namesmodule@0.0.1.tgz
Der eigene Server
Express
Mit http-Moduls einen Webserver programmieren = viel zu aufwändig, denn es gibt zu viele Details des HTTP-Protokolls, die programmiert werden müssten. Das Webserver-Framework Express (https://expressjs.com/) vereinfacht Arbeit wesentlich. Open Source Software Express unterstützt uns bei Auslieferung von Dateien, dem Routing, dem View-Rendering und ist modular erweiterbar Code ist wesentlich flexibler und ausbaufähig:
eigentliche Arbeit des Verarbeitens einer Anfrage erfolgt mit get(…)-Methode
- weisen damit Express an, GET-Anfrage auf URL-Pfad / mit Callback im zweiten
Argument zu beantworten. Antwort-Objekt res besitzt hier eine send(…)-Methode,
die bereits passende Default-HTTP-Header setzt nur Seiteninhalt muss noch übergeben werden
Routing
Zuordnen von Pfaden wie / oder /seite.html zu Callbacks nennt Express Routing.
Können mit app.get() mehrere Routen definieren. Ganze Seite so zu routen,
wäre nicht sehr wartungsfreundlicher Ansatz. Express hat eingebaute Funktion
express.static(…), die automatisch alle Dateien aus Verzeichnis bereitstellt.
Mit Aufruf von express.static(…) wird Name des Verzeichnisses übergeben,
in dem Dateien liegen. Express stellt alle Dateien im angegebenen Verzeichnis,
inkl. Unterverzeichnissen, über HTTP bereit. Die Notwendigkeit, einzelne Routen
zu definieren, entfällt. express.static(…) und Routen lassen sich kombinieren.
Bei Konflikt - gerouteter Pfad identisch mit existierender Datei - ermittelt
Express Vorrang aus Reihenfolge der Aufrufe im JavaScript-Code, wobei erster Aufruf gewinnt.
Middleware
Funktionalität von Express lässt sich durch Middleware erweitern.
Middleware = wesentlicher Bestandteil von Express
Dies sind Funktionen, die für Verarbeitung von Anfragen hintereinandergeschaltet
werden können und dabei unterschiedliche Funktionalitäten wie beispielsweise
Parsen von Cookies oder auch komplexere Themen wie Sicherheit und Zugriffskontrolle
implementieren. Jede Middleware-Funktion hat Zugriff auf Anfrageobjekt (Request-Objekt),
Antwortobjekt (Response-Objekt) und auf jeweils nächste Middleware-Funktion wird
mit app.use(…) geladen mit Aufruf next() wird Abarbeitung weitergegeben hat
keinen einfluss auf app.get() -> wird davor ausgeführt.
Beispiel für Verwendung:
Nutzung des Middleware-Package body-parser : https://github.com/expressjs/body-parser
Um Inhalt einer HTTP-Anfrage zu parsen - beispielsweise um POST Inhalte abzufangen.
Geparster Inhalt ist innerhalb der Callback-Funktion über Eigenschaft body
des Anfrageobjekts erreichbar. Um Middleware-Funktion zu verwenden, auch hier
Übergabe des entsprechenden Aufrufs an Methode use() am Anwendungsobjekt.
extended-Angabe in bodyParser.urlencoded erlaubt zu wählen, ob URL-codierte
Daten mit Querystring-Bibliothek (false) oder qs-Bibliothek (true) analysiert
werden sollen
“erweiterte” Syntax ermöglicht Codierung umfangreicher Objekte und Arrays ins
URL-codierte Format, wodurch JSON-ähnliche Erfahrung mit URL-Codierung ermöglicht
wird.
Liste der Middleware Module auf Express-Seite: http://expressjs.com/en/resources/middleware.html