JavaScript im Backend – auf dem Server
Während bei den klassischen Programmiersprachen wie C und PHP Datei- und Datenbankzugriffe die Anwendung blockieren, kann Javascript Anweisungen im Hintergrund asynchron durchführen und per Callback auf Ergebnisse reagieren und verkraften tausende von gleichzeitigen Verbindungen. Typische Anwendungen für Node.js sind Chat mit Live Updates, Blogs, Social Networks, alles was nicht sehr CPU-intensiv ist.
Einführung in Node.js auf YouTube (eng., rd. 1 Stunde)
Mit Node.js können Frontend und Backend in derselben Programmiersprache entwickelt werden: Ein Node.js-Server hat Zugriff auf das Dateisystem und Datenbanken und kann Emails versenden, so dass das komplette Projekt mit Javascript umgesetzt werden können. Wie ein klassischer Apache-Server bietet Node Funktionen wie Autorisation und Sessions.
Für die Client-Seite mit HTML, CSS und Javascript macht es keinen Unterschied, ob sie auf einem Node.js oder Apache-Server basiert.
Node – 1 Klick-Installation
Node.js kann fertig kompiliert direkt von Node.js als Paket geladen und mit einem Klick installiert werden.
Mit der Installation von Node.js wird zugleich auch npm installiert, ein Package Manager für Node.js. Für Node.js gibt es eine große Anzahl von Erweiterungen – Pakete –, die entweder bereits lokal mit der Installation von Node.js als globale Objekte ausgeliefert werden oder mit Hilfe von npm installiert werden.
Übersicht über die globalen Objekte: https://nodejs.org/api/globals.html
Node: Das Terminal ist die Konsole
Mit Node.js läuft Javascript auf dem Server und nicht im Browser. Es gibt keine grafische Benutzerschnittstelle, sondern nur das Terminal. Das Terminal ist für Node, was die Konsole für das Script im Browser ist.
Terminal öffnen und node -v eingeben, um die Installation zu prüfen und mit einem weiteren node-Befehl eine kleine Rechenaufgabe stellen.
emma@mac-user ~ % node -v v22.1.0 emma@mac-user ~ % node Welcome to Node.js v18.16.1. Type ".help" for more information. > 10 * 20 200
Um Node zu beenden, zwei mal Ctrl C drücken.
Javascript vs Node.js
Es gibt wichtige Unterschiede zwischen Javascript im Browser und Node. So gibt es kein Window und kein Document, anstelle von Window tritt global, anstelle von document haben wir process.
window | global |
document | process |
history | module |
location | __filename |
navigator | require |
Ein weiterer Unterschied zwischen Node und Vanilla Javascript: Node hat eine Liste von Core-Modulen, z.B. fs für den Lese-Schreibzugriff auf Dateien, http zum Aufbau eines rudimentärern Webservers, os für den Zugriff auf Informationen des Betriebssystems. Über die mitgelieferten Module hinaus können weitere Module mit npm installiert werden.
Node.js-Core Module: os, path
Ein Verzeichnis – z.B. test – anlegen (z.B. auf dem Desktop) und im Terminal in dieses Verzeichnis gehen. Dazu eine Datei server.js in diesem Verzeichnis erzeugen.
console.log ("Hallo Node!")
Im Terminal node server.js eingeben und im Terminal erscheint Hallo Node!.
Module werden in Node mit require() importiert. Zwar unterstützt Node seit Version 13 auch den ECMAScript-Modulstandard import, aber require braucht keine Konfiguration und kann direkt in die server.js-Datei eingesetzt werden.
const os = require("os"); console.log(os.type()); console.log(os.version()); console.log(os.homedir()); console.log(__dirname); console.log(__filename);
__dirname ist das Verzeichnis der Datei server.js, __filename der volle Dateiname. Kommt zusammen mit dem os-Modul, dafür muss kein Modul installiert werden.
path() ist ein weiteres Modul, das mit Node ausgeliefert wird.
const os = require("os"); const path = require("path"); console.log(path.dirname(__filename)); console.log(path.basename(__filename)); console.log(path.extname(__filename)); console.log(path.parse(__filename));
path.dirname(__filename) liefert dasselbe wie __dirname, path.basename(__filename) entspricht __filename.
path.parse(__filename) gibt ein Objekt mit Informationen zur Datei zurück.
server.js .js { root: '/', dir: '/Users/myuser/Desktop/test', base: 'server.js', ext: '.js', name: 'server' }
fs-Modul – Dateien lesen und schreiben
Das fs-Modul in Node.js ist ebenfall ein mitgeliefertes Modul und muss nicht installiert werden.
import fs from "fs"; // import statt require nur, wenn alle Module importiert werden!
Die wichtigsten sind sicher writeFile, readFile und appendFile.
Für viele der Module gibt es zwei Versionen, wie z.B. chmod() und chmodSync(). chmod() ist die nicht-blockierende Version, chmodSync ist synchrone blockierende Version, die auf die Beendigung der Methode wartet. In den meisten Fällen wird eher die asynchrone Version eingesetzt.
// readFile – mit Callback (default) Pfad Kodierung Fehler Daten │ │ │ │ ▼ ▼ ▼ ▼ fs.readFile("./test.txt", "utf8", (err, data) => { if (err) throw err; console.log(data); });
Die synchrone Methode – kein Callback
const data = fs.readFileSync("./test.txt", "utf8"); console.log ("data Sync", data)
fs-Version mit Promises, anderer Import. Es gibt also sowohl die Dot then-Syntax als auch await.
import fs from "fs/promises"; fs.readFile("./test.txt", "utf8") .then((data) => console.log (data)) .catch((err) => console.log (err));
Und als vierte Option readFile() in async await-Syntax und gleich mit writeFile().
const readFile = async () => { try { const data = await fs.readFile("./test.txt", "utf8"); console.log (data); } catch (error) { console.log (error); } } const writeFile = async () => { try { await fs.writeFile("./test.txt", "So, und jetzt schreib mal was in die Datei"); console.log ("Was in die Datei schreiben"); } catch (error) { console.log (error); } } writeFile(); readFile();
Die Datei text.txt wurde überschrieben. fileAppend() würde etwas an die Datei anhängen.
Einen Node-Server erzeugen
Einen Ordner anlegen, z.B. nodeStart. Im Terminal mit cd (change directory) in diesen Ordner wechseln. Node initialisieren:
npm init
npm legt eine Datei package.json in diesem Ordner an und stellt eine Handvoll Fragen, die man für den Anfang alle mit den Vorgaben beantworten kann.
{
"name": "morenode",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": ""
}
Wie in jeder anderen Programmierumgebung und Programmiersprache werden unter dem Ordner nodeStart alle Ordner und Dateien für die Anwendung liegen. Jetzt erst einmal einen Node-Server in server.js anlegen, so wie es als main in package.json vereinbart ist.
http-Modul: Webserver mit dem http-Modul anlegen
import http from "http"; const PORT = 8000; const server = http.createServer((req, res) => { res.setHeader("Content-Type", "text/html"); res.write("<h1>Hallo Welt</h1>"); res.end(); }); // die letzte Anweisung im Server-Skript ist immer server.listen server.listen(8000, () => { console.log(`Server running on port ${PORT}`) })
Quelle node-hello-world auf GitHub.
Diese wenigen Zeilen geben ein vollständiges HTML-Dokument im Browser aus.
<html> <head> </head> <body> <h1>Hallo Welt</h1> </body> </html>
Ganz gleich, ob im Browser localhost:8000 oder localhost:8000/meineseite aufgerufen wird: Da die URL nicht geprüft und entsprechend verzweigt ist, landet der Browser immer auf der Seite »Hallo Welt«.
nodemon
Normalerweise muss der Server nach jeder Änderung erneut gestartet werden, um die Änderungen zu übernehmen. Das Node-Modul nodemon überwacht den Code und startet den Server bei jeder Änderung automatisch erneut. nodemon wird mit npm installiert.
Zusätzlich zu den Modulen / Plugins, die in der Installation von Node.js installiert werden, stehen weitere Module mit ihrer Dokumentation auf npmjs.com bereit, die von Dritten für Node entwickelt wurden.
NPM-Module müssen nicht heruntergeladen werden, sondern werden direkt im Terminal mit npm entweder global oder lokal installiert.
npm install -g nodemon | +-- global
Mit -g wird nodemon global installiert, so dass es in jedem Projekt zur Verfügung steht, aber i.A. wird nodemon nicht global, sondern nur für die Dauer der Entwicklung eingesetzt und darum eher mit -D als Development Dependency installiert.
npm install -D nodemon | +-- als Development Dependency
Node würde jetzt gestartet mit
nodemon index.js
Um schnell und einfach zwischen Entwicklungsmodus und Produktion umzuschalten, kann man eine kleine Änderung in package.json einsetzen.
"scripts": { "start": "node index", "dev": "nodemon index" },
Ein normaler Start des Servers geschieht weiterhin mit
node index.js
Als Entwicklungsserver starten:
npm run dev
Die Installation von nodemon hat einen Ordner node_modules erzeugt, falls nicht bereits zuvor ein anderes Modul installiert wurde. Eine lange Liste von Modulen ist damit im Ordner node_modules entstanden: Das sind Pakete, von denen nodemon abhängig ist, und die selber wieder Pakete brauchen.
Zudem wurde ein package-lock.json mit Details zu den Paketen und Versionen angelegt.
Wenn die Anwendung weiter gegeben wird, wird der Ordner node-modules nicht mit ausgeliefert, sondern erst bei der Installation auf einem anderen System dort angelegt bzw. aufgefüllt.
Eine weditere Datei sollte u.U. noch manuell angelegt werden: .gitignore (man beachte den Punkt!). Wenn der Ordner auf github übertragen wird, muss der node_modules-Ordner nicht übertragen werden, denn die Installation erfährt aus package.json, welche Module erforderlich sind. In .gitignore eine Zeile node_modules setzen.
Export, Import
Funktionen, Objekte, Arrays und Klassen werden mit export exportiert und mit require importiert. Eine Datei tools.js anlegen:
function sineWave(c) { let s = Math.sin(c); let x = 500 - c * 700; // Bewegung auf der x-Achse let y = s * 90 + 250; // Bewegung auf der y-Achse return [x, y]; }; module.exports = sineWave; // Ohne Klammern
Ins Hauptprogramm importieren
const sineWave = require("./tools"); // Braucht kein .js
Um import anstelle des älteren require zu verwenden, braucht package.json einen Eintrag type: "module",. Das ausgelagertes Skript enthält ein Array.
const posts = [ {id: 1, title: "Post Eins"}, {id: 2, title: "Post Zwei"}, {id: 3, title: "Post Drei"}, {id: 4, title: "Post Vier"} ]; const getPosts = () => posts; export { getPosts }; // oder in einer Zeile // export getPosts = () => posts;
Im Hauptprogramm
import { getPosts } from "./postController.js"; console.log (getPosts())
Ein einfacher Router für Node.js
Der Router spricht unterschiedliche URLs an.
const server = http.createServer((req, res) => { if (req.url === "/") { res.writeHead(200, { "Content-Type": "text/html" }); res.end("Hallo Welt
"); } else if (req.url === "/about") { res.writeHead(200, { "Content-Type": "text/html" }); res.end("About
"); } else { res.writeHead(404, { "Content-Type": "text/html" }); res.write("Datei nicht gefunden
"); } });
Node.js Request und Response mit Postman testen
Postman ist ein Werkzeug für die API-Entwicklung, mit dem HTTP-Anfragen wie GET, POST, PUT, DELETE und PATCH unter einer grafischen Oberfläche gestestet werden.
Bei der Entwicklung von Node.js-Anwendungen werden die »Endpunkte« geprüft:
- Mit einer GET-Anfrage Daten abrufen
- mit POST Daten an den Server senden
Dabei sendet, empfängt, erzeugt Postman Daten in verschiedenen Formaten von JSON über Form-Daten bis hin zu URL-Parametern.
Mit Postman müssen während der Entwicklung und Testen des Servers keine zusätzlichen Anwendungen auf der Client-Seite angelegt werden, die Requests senden und Antworten empfangen.
Middleware
Als Middleware werden im Wesentlichen Funktionen bezeichnet, die Zugriff auf das Request und Response-Objekt haben, weil sie sprichwörtlich in der Mitte zwischen hereinkommenden Requests und ausgehenden Antworten sitzen.
Express
Webseiten für einen Node.js-Server werden wie auf einem Apache-Server angelegt: Statische Inhalte werden in HTML-Dateien aufbereitet, dynamische Inhalte werden vom Server erstellt. Express ist ein Modul für webbasierte Anwendungen und wird für einzelne Seiten und hybride Anwendungen eingesetzt.
Express ist ein Node.js-Modul, aber wird nicht mit Node geliefert, sondern muss installiert werden (z.B. mit npm). Das Modul muss also auch mitgeliefert werden, wenn die Anwendung weiter gegeben oder an anderer Stelle installiert wird.
Express wird mit npm installiert und im Verzeichnis gespeichert (s).
npm install -s express
Alle installierten Module werden werden in package.json gesammelt. Diese Json-Datei erzeugt man am einfachsten mit dem Aufruf npm init in der Console im Verzeichnis der Anwendung.
{
"name": "express",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.2"
}
}
Eine statische HTML-Datei index.html anlegen. Der Express-Minimalserver braucht nicht einmal eine Handvoll Zeilen.
const express = require ("express"); const app = express(); app.use(express.static(__dirname)); app.listen (3000);
Node.js Anwendungen auf dem Server
Was bringt Node.js als Webserver? Bei einem klassischen Server haben wir PHP und Datenbanken im Backend und Javascript im Frontend. Mit Node.js haben wir Javascript sowohl im Frontend als auch im Backend, und das hat unbestritten Vorteile: Frontend und Backend können Code, Algorithmen und Datenstrukturen teilen, wir müssen nicht zwischen zwei Programmiersprachen hin und her schalten und der Pflegeaufwand wird reduziert.
Ein Beispiel ist JSON, das ideal als Datenstruktur sowohl im Backend als auch im Backend ist. Mit Node.js und Javascript wird keine Konvertierung gebraucht. Javascript ist sicher nicht die ultimative Lösung, aber effizient für viele Anwendungen.