Alles, was du brauchst, um endlich zu verstehen, was wirklich hinter Node.js steckt

27.07.2022Ricky Elfner
Tech JavaScript Node.js

Die meisten von euch werden von Node.js auf jeden Fall schon gehört haben und wissen sicher auch, wo es Anwendung findet. Doch viele kennen die Arbeitsweise und die Funktionen, die es bereitstellt, gar nicht so genau. Deshalb werden wir uns in diesem Techup Node.js einmal genauer anschauen. 🤠

Zunächst einmal handelt es sich bei Node.js nicht um eine Programmiersprache und auch nicht um ein Framework. Es ist mehr eine Laufzeitumgebung, welche auf JavaScript aufbaut und genutzt wird, um verschiedenste Frameworks erst überhaupt richtig nutzen zu können. Node.js wurde 2009 von Ryan Dahl entwickelt. Vor dessen Einführung wurde JavaScript vor allem im Frontend genutzt, um Interaktionen mit einem Benutzer auszuführen und dynamische Inhalte zu generieren. Doch dies änderte sich mit Node.js, denn nun war es auch möglich, serverseitige Programmierung mittels JavaScript zu nutzen. Hierdurch wird der JavaScript Code nicht mehr nur clientseitig ausgeführt, sondern kann auch auf dem Backend ausgeführt werden. Dabei verwendet Node.js die Laufzeitumgebung V8, welche für Google Chrome entwickelt wurde. Eine weitere wichtige Eigenschaft von Node.js ist die event-basierte Arbeitsweise, somit reagiert es auf Eingaben von einem Benutzer oder auf http-Aufrufe.

Anwendungsbereiche

Mittlerweile findet man Node.js in den verschiedensten Bereichen. Auch grosse Namen wie Twitter, Spotify oder eBay setzen auf diese Runtime. Dabei kann Node.js innerhalb der unterschiedlichsten Applikationen verwendet werden. Beispiele hierfür sind:

  • Data Streaming
  • IoT Applikationen
  • Complex SPAs
  • REST API Server
  • Web Applikationen
  • Real Time Chats

Timeline

In der folgenden Grafik ist zu sehen, wie sich Node.js seit der ersten Version im Jahr 2009 entwickelt hat.

nodejs_03

Aufbau

nodejs_01

Libuv

Wie bereits erwähnt setzt Node.js anstatt von thread-basierten Events auf asynchrone Kommunikation. Dies wird auch als Non-Blocking I/O definiert, denn hierbei wird nicht für jede Anfrage ein eigener neuer Thread gestartet, sondern stattdessen Single-Threads verwendet. Diese werden nacheinander in einer Endlosschleife abgearbeitet, wodurch ein Blockieren des Ablaufs vermieden wird. Für das Abarbeiten der Threads wird Libuv verwendet. Dabei handelt es sich um eine in C geschriebene “multi-platform support library”, welche sich auf asynchrone I/O spezialisiert hat. Libuv wurde bewusst für Node.js entwickelt, findet jedoch mittlerweile auch in Levit, Julia und anderen Projekten Anwendung.

Dies hat in dem Fall den Vorteil, dass, wenn eine I/O-Operation beispielsweise Zugriff auf eine Datenbank benötigt, nicht der Thread blockiert wird, sondern Node.js die eigentliche Aufgabe erst beendet, wenn die Antwort zurückkommt. Node.js kann dadurch in der Zwischenzeit mit anderen Aufgaben fortfahren.

Aufbau von Libuv

Der Libuv-Aufbau kann eigentlich in zwei Schichten unterteilt werden. Der obere Layer mit Network I/O, File I/O, DNS Operations und User Code, definiert die Schnittstellen, welche von den Libuv-Usern verwendet werden können. Die darunterliegende Schicht ist für das Handling von zyklischen I/O Abfragen zuständig, sowie die Worker Threads. IOCP ist für die I/O Abfrage von Fenstern zuständig, während epoll, kqueue und Event Ports für die Systeme zuständig ist.

image_01

Zwei weitere Konzepte, die man kennen sollte, sind Handles und Requests. Bei Handles handelt es sich um Abstraktionen von Ressourcen, wie beispielsweise TCP und UDP Sockets. Sobald ein Job erledigt ist, ruft der Handle den zugehörigen Callback auf. Diese werden auch long-lived objects genannt, da sie diese Aufgabe solange übernehmen, wie sie aktiv sind. Requests dagegen sind short-lived operations. Diese Operationen werden direkt über ein Handle ausgeführt, und sind vergleichbar mit Funktionen oder Methoden.

Zum Schluss ist noch der Thread Pool zu erwähnen, welcher oben im Schaubild zu sehen ist. Dieser ist zuständig für File I/O und die DNS-Suche. Die Callbacks werden jedoch im Haupt-Thread ausgeführt. Da es seit Node Version 10.5 möglich ist, auch JavaScript über die Worker-Threads parallel auszuführen, kann die standardmässige Grösse von 4 mittels einer Umgebungsvariable erhöht werden.

Event Loop

Eine der wichtigsten Eigenschaften von Node.js ist der Event Loop, oder auch teilweise Event Stack genannt. Dieser Loop beschreibt, wie es möglich ist, dass Node.js asynchron arbeitet, und non-blocking I/O erreicht. Der Node.js JavaScript Code läuft auf einem Single Thread, somit passiert genau eine Sache zur gleichen Zeit. Dadurch muss man sich auch keine Gedanken über Parallelitätsprobleme machen. Man muss jedoch beim Schreiben von Code darauf achten, dass man keinen Thread durch Network Calls oder Infinite Loops blockiert.

Beispielsweise hat jeder Chrome-Tab einen eigenen Event Loop, damit jeder Prozess isoliert läuft und nicht andere Tabs oder sogar den ganzen Browser blockiert. Somit gibt es mehrere verschiedene Event Loops. Bis zu diesem Punkt mussten die Server pro Aufgabe immer einen eigenen Thread starten. Der Event Loop überprüft dabei die ganze Zeit, ob innerhalb des Call Stacks eine Funktion vorhanden ist, die ausgeführt werden muss. Dabei arbeitet dieser Stack nach dem LIFO-Prinzip (Last In, First out).

Job Queue

2015 wurde mittels ECMA Script das Konzept der Job Queue eingeführt. Dabei werden Promises verwendet, um den Vorteil zu nutzen, dass Ergebnisse einer async-Funktion so schnell wie möglich verfügbar sind. Sie werden nämlich nicht an das Ende des Call Stacks hinzugefügt, sondern werden direkt nach Beendigung der aktuellen Funktion aufgerufen.

Node Core

Zu dem Node Core gehört vor allem die V8 Engine, bei der es sich um einen JIT-Compiler handelt. Dies ermöglicht den ständigen Wechsel zwischen Kompilieren und ausführen von Code. Der grosse Vorteil, der geschaffen wird, ist, dass die Engine Informationen sammeln kann, welche im nächsten Schritt beim Kompilieren direkt weiterverwendet werden können. Zusätzlich zu dieser und der Libuv Dependency, kommen noch Libraries wie OpenSSL oder HTTP-Parser. Diese Wrapper sind notwendig, da die V8 Engine sehr restriktiv ist und beispielsweise keine Zugriffe auf das Dateisystem zulässt.

Der Package Manager

Ein weiteres Feature, welches Node.js bietet, ist der Node Package Manager. Dieser erlaubt es, weitere Module zu nutzen. Zwar werden von Haus aus schon einige nützliche Module mit ausgeliefert, doch die Möglichkeiten zur Erweiterung sind hier enorm. Die Packages können mittels CLI (npm) installiert werden. Für viele grundlegende Dinge gibt es bereits Module, die gewünschte Funktionen bereitstellen, mit denen man viel Zeit sparen kann. Ebenfalls ist es hierdurch möglich, eine gewisse Standardisierung bereitzustellen.

Frontend oder Backend

Eine ebenfalls oft gestellte Frage ist, ob Node.js im Frontend oder im Backend eingesetzt wird. Im Grunde genommen kann man es für beides verwenden, da es mittlerweile auch Node.js-Frameworks wie Express.js gibt. Auch innerhalb von dem weitverbreitetem MERN Stack (MongoDB, Express, React, Node) wird Express.js verwendet. Ein weiterer Punkt dabei ist die erhöhte Effizienz, da die Entwickler nicht mehr zwischen verschiedenen Programmiersprachen wechseln müssen.

Vorteile

Da JavaScript zu den Sprachen gehört, welche relativ leicht zu erlernen sind, hat man dank Node.js den Vorteil, dass nun auch JavaScript für backend-seitige Aufgaben verwendet werden kann. Zudem ist Node.js stark skalierbar und dazu noch schnell in der Verarbeitung von Daten. Dank asynchronen Events kann Non-blocking I/O erreicht werden, da man nicht mehr warten muss, bis eine Aufgabe erledigt ist. Stattdessen wird mittels Callbacks erst mit der Aufgabe weitergemacht, wenn das notwendige Resultat vorhanden ist. Durch NPM kann man vorhandene Funktionen verwenden, und sich auf sein eigentliches Programm konzentrieren. Da die Community rund um Node.js mittlerweile sehr gross ist, kann man hier auf guten Support in den verschiedensten Foren hoffen.

Nachteile

Beim Schreiben von eigenem Code muss beachtet werden, dass alles mit Callbacks aufgebaut werden muss. Dies hat auch zur Folge, dass der Code hier schnell unübersichtlich und schwer zu Verwalten werden wird. Ein weiterer Punkt, den man beachten sollte, ist das Zugreifen auf Datenbanken, da diese meist nur eine gewisse Anzahl an Requests verarbeiten kann. Hier müsste man dann wieder trotzdem ein Queue System verwenden. Da Node.js single-threaded ist, ist diese Runtime nicht gut geeignet, wenn man viele CPU-intensive Tasks hat.

Node.js Frameworks and Tools

  • AdonisJS: Dabei handelt es sich um ein fully-featured TypeScript-basiertes Framework, welches sich stark auf den Entwickler, sowie auf Stabilität und Vertrauen fokussiert. Zusätzlich zählt es zu den schnellsten Node.js Frameworks.
  • Gatsby: Dies ist ein Static Site Generator, welcher auf React basiert und GraphQL unterstützt. Der grosse Vorteil dabei liegt in der Auswahl von Plugins.
  • Next.js: Ein weiteres Framework, welches auf React aufbaut. Wichtige Funktionen wie Hybrid Static & Server Rendering, TypeScript Support, Smart Bundling, Route pre-fetching werden von Haus aus mitgeliefert.
  • Express: Express stellt einen der simpelsten und leistungsfähigsten Wege zur Verfügung, um einen Webserver bereitzustellen.

Fazit

Wenn man sich zuvor Gedanken macht, für was man seine Anwendung nutzen möchte, und dabei ausschliessen kann, dass es rechenintensive Aufgaben gibt, macht es auf jeden Fall Sinn, Node.js zu nutzen. Deshalb wird auch in vielen Frameworks direkt auf Node.js gesetzt. Besonders bei REST-Diensten, Streaming- oder Single-Page-Applikationen kann Node.js seine Stärke zeigen. Ebenfalls kann man von der grossen Community rundherum profitieren.

Jedoch sollte man sich auch der Nachteile von Node.js bewusst sein. Namentlich potentiell unübersichtlicher Code durch eine grosse Anzahl von Callbacks, potentielle Limitierung der Datenbankzugriffe, und die Nichteignung für CPU-intensive Tasks.

Demnächst wollen wir uns auf jeden Fall einmal Deno genauer anschauen und sehen, was die unterschiede zu Node.js sind. Was es anders und besser oder schlechter macht. Bleibt dran! 💪

Ricky Elfner

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.