Ist es an der Zeit, Node 8 zu verwenden?

Veröffentlicht: 2022-03-11

Knoten 8 ist raus! Tatsächlich ist Node 8 jetzt lange genug draußen, um eine solide Nutzung in der realen Welt zu sehen. Es wurde mit einer schnellen neuen V8-Engine und neuen Funktionen geliefert, darunter async/await, HTTP/2 und async-Hooks. Aber ist es bereit für Ihr Projekt? Lass es uns herausfinden!

Anmerkung des Herausgebers: Sie wissen wahrscheinlich, dass Node 10 (mit dem Codenamen Dubnium ) ebenfalls draußen ist. Wir haben uns aus zwei Gründen entschieden, uns auf Node 8 ( Carbon ) zu konzentrieren: (1) Node 10 tritt gerade in seine Phase der langfristigen Unterstützung (LTS) ein, und (2) Node 8 markiert eine bedeutendere Iteration als Node 10 .

Leistung in Node 8 LTS

Wir beginnen mit einem Blick auf die Leistungsverbesserungen und neuen Funktionen dieser bemerkenswerten Version. Ein wichtiger Verbesserungsbereich ist die JavaScript-Engine von Node.

Was genau ist überhaupt eine JavaScript-Engine?

Eine JavaScript-Engine führt Code aus und optimiert ihn. Es könnte ein Standardinterpreter oder ein Just-in-Time (JIT)-Compiler sein, der JavaScript in Bytecode kompiliert. Die von Node.js verwendeten JS-Engines sind alle JIT-Compiler, keine Interpreter.

Der V8-Motor

Node.js hat von Anfang an die JavaScript-Engine Chrome V8 oder einfach V8 von Google verwendet. Einige Node-Releases werden zur Synchronisierung mit einer neueren Version von V8 verwendet. Achten Sie jedoch darauf, V8 nicht mit Node 8 zu verwechseln, wenn wir hier die V8-Versionen vergleichen.

Darüber kann man leicht stolpern, da wir in Softwarekontexten oft „v8“ als Slang oder sogar offizielle Kurzform für „Version 8“ verwenden, sodass einige „Node V8“ oder „Node.js V8“ mit „NodeJS 8“ verschmelzen könnten “, aber wir haben dies in diesem Artikel vermieden, um die Dinge klar zu halten: V8 bedeutet immer die Engine, nicht die Version von Node.

V8-Version 5

Node 6 verwendet V8 Release 5 als JavaScript-Engine. (Die ersten Point-Releases von Node 8 verwenden ebenfalls V8-Release 5, aber sie verwenden ein neueres V8-Point-Release als Node 6.)

Compiler

V8-Releases 5 und früher haben zwei Compiler:

  • Full-Codegen ist ein einfacher und schneller JIT-Compiler, produziert aber langsamen Maschinencode.
  • Crankshaft ist ein komplexer JIT-Compiler, der optimierten Maschinencode erzeugt.
Fäden

Im Grunde verwendet V8 mehr als einen Thread-Typ:

  • Der Haupt-Thread ruft Code ab, kompiliert ihn und führt ihn dann aus.
  • Sekundäre Threads führen Code aus, während der Haupt-Thread den Code optimiert.
  • Der Profiler-Thread informiert die Laufzeit über leistungsschwache Methoden. Crankshaft optimiert diese Methoden dann.
  • Andere Threads verwalten die Garbage Collection.
Zusammenstellungsprozess

Zuerst führt der Full-Codegen-Compiler den JavaScript-Code aus. Während der Code ausgeführt wird, sammelt der Profiler-Thread Daten, um zu bestimmen, welche Methoden die Engine optimieren wird. In einem anderen Thread optimiert Crankshaft diese Methoden.

Themen

Der oben erwähnte Ansatz hat zwei Hauptprobleme. Erstens ist es architektonisch komplex. Zweitens verbraucht der kompilierte Maschinencode viel mehr Speicher. Die verbrauchte Speichermenge ist unabhängig davon, wie oft der Code ausgeführt wird. Auch Code, der nur einmal ausgeführt wird, nimmt ebenfalls eine beträchtliche Menge an Speicher ein.

V8-Version 6

Die erste Node-Version, die die V8-Release-6-Engine verwendet, ist Node 8.3.

In Version 6 hat das V8-Team Ignition und TurboFan entwickelt, um diese Probleme zu mindern. Ignition und TurboFan ersetzen Full-Codegen bzw. CrankShaft.

Die neue Architektur ist einfacher und verbraucht weniger Speicher.

Ignition kompiliert JavaScript-Code in Bytecode anstelle von Maschinencode, wodurch viel Speicher gespart wird. Anschließend generiert TurboFan, der optimierende Compiler, aus diesem Bytecode optimierten Maschinencode.

Spezifische Leistungsverbesserungen

Lassen Sie uns die Bereiche durchgehen, in denen sich die Leistung in Node 8.3+ im Vergleich zu älteren Node-Versionen geändert hat.

Objekte erstellen

Das Erstellen von Objekten ist in Node 8.3+ etwa fünfmal schneller als in Node 6.

Funktionsgröße

Ob eine Funktion optimiert werden soll, entscheidet der V8-Motor anhand mehrerer Faktoren. Ein Faktor ist die Funktionsgröße. Kleine Funktionen werden optimiert, lange Funktionen hingegen nicht.

Wie wird die Funktionsgröße berechnet?

Die Kurbelwelle im alten V8-Motor verwendet die „Zeichenanzahl“, um die Funktionsgröße zu bestimmen. Leerzeichen und Kommentare in einer Funktion verringern die Chancen, dass sie optimiert wird. Ich weiß, das mag Sie überraschen, aber damals konnte ein Kommentar die Geschwindigkeit um etwa 10 % reduzieren.

In Node 8.3+ beeinträchtigen irrelevante Zeichen wie Leerzeichen und Kommentare die Funktionsleistung nicht. Warum nicht?

Weil der neue TurboFan keine Zeichen zählt, um die Funktionsgröße zu bestimmen. Stattdessen werden AST-Knoten (Abstract Syntax Tree) gezählt, sodass effektiv nur tatsächliche Funktionsanweisungen berücksichtigt werden. Mit Node 8.3+ können Sie beliebig viele Kommentare und Leerzeichen hinzufügen.

Array -ifizierende Argumente

Reguläre Funktionen in JavaScript tragen ein implizites Array -ähnliches argument .

Was bedeutet Array -ähnlich?

Das arguments -Objekt verhält sich ähnlich wie ein Array. Es hat die Eigenschaft length , aber es fehlen die eingebauten Methoden von Array wie forEach und map .

So funktioniert das arguments -Objekt:

 function foo() { console.log(arguments[0]); // Expected output: a console.log(arguments[1]); // Expected output: b console.log(arguments[2]); // Expected output: c } foo("a", "b", "c");

Wie könnten wir also das arguments -Objekt in ein Array umwandeln? Durch die Verwendung des knappen Array.prototype.slice.call(arguments) .

 function test() { const r = Array.prototype.slice.call(arguments); console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output: [2, 4, 6]

Array.prototype.slice.call(arguments) beeinträchtigt die Leistung in allen Node-Versionen. Daher funktioniert das Kopieren der Schlüssel über eine for -Schleife besser:

 function test() { const r = []; for (index in arguments) { r.push(arguments[index]); } console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output [2, 4, 6]

Die for -Schleife ist etwas umständlich, oder? Wir könnten den Spread-Operator verwenden, aber er ist in Node 8.2 und darunter langsam:

 function test() { const r = [...arguments]; console.log(r.map(num => num * 2)); } test(1, 2, 3); // Expected output [2, 4, 6]

Die Situation hat sich in Node 8.3+ geändert. Jetzt wird der Spread viel schneller ausgeführt, sogar schneller als eine for-Schleife.

Partielle Anwendung (Currying) und Bindung

Beim Currying wird eine Funktion, die mehrere Argumente verwendet, in eine Reihe von Funktionen zerlegt, wobei jede neue Funktion nur ein Argument akzeptiert.

Nehmen wir an, wir haben eine einfache add . Die Curry-Version dieser Funktion nimmt ein Argument, num1 . Es gibt eine Funktion zurück, die ein anderes Argument num2 und die Summe von num1 und num2 :

 function add(num1, num2) { return num1 + num2; } add(4, 6); // returns 10 function curriedAdd(num1) { return function(num2) { return num1 + num2; }; } const add5 = curriedAdd(5); add5(3); // returns 8

Die bind -Methode gibt eine Curry-Funktion mit einer knapperen Syntax zurück.

 function add(num1, num2) { return num1 + num2; } const add5 = add.bind(null, 5); add5(3); // returns 8

bind ist also unglaublich, aber in älteren Node-Versionen langsam. In Node 8.3+ ist bind viel schneller und Sie können es verwenden, ohne sich Gedanken über Leistungseinbußen machen zu müssen.

Experimente

Es wurden mehrere Experimente durchgeführt, um die Leistung von Knoten 6 mit Knoten 8 auf hohem Niveau zu vergleichen. Beachten Sie, dass diese auf Node 8.0 durchgeführt wurden, sodass sie die oben erwähnten Verbesserungen nicht enthalten, die dank des V8 Release 6-Upgrades spezifisch für Node 8.3+ sind.

Die Server-Renderingzeit in Node 8 war um 25 % kürzer als in Node 6. In großen Projekten konnte die Anzahl der Serverinstanzen von 100 auf 75 reduziert werden. Das ist erstaunlich. Das Testen einer Suite von 500 Tests in Node 8 war 10 % schneller. Webpack-Builds waren 7 % schneller. Im Allgemeinen zeigten die Ergebnisse eine spürbare Leistungssteigerung in Node 8.

Funktionen von Knoten 8

Geschwindigkeit war nicht die einzige Verbesserung in Node 8. Es brachte auch mehrere praktische neue Funktionen – vielleicht am wichtigsten, async/await .

Async/Await in Knoten 8

Callbacks und Promises werden normalerweise verwendet, um asynchronen Code in JavaScript zu verarbeiten. Rückrufe sind dafür bekannt, nicht wartbaren Code zu erzeugen. Sie haben in der JavaScript-Community für Chaos (speziell als Callback-Hölle bekannt) gesorgt. Promises retteten uns lange vor der Callback-Hölle, aber ihnen fehlte noch die Sauberkeit von synchronem Code. Async/await ist ein moderner Ansatz, mit dem Sie asynchronen Code schreiben können, der wie synchroner Code aussieht.

Und während async/await in früheren Node-Versionen verwendet werden konnte, erforderte es externe Bibliotheken und Tools – zum Beispiel eine zusätzliche Vorverarbeitung über Babel. Jetzt ist es nativ verfügbar, sofort einsatzbereit.

Ich werde über einige Fälle sprechen, in denen async/await konventionellen Versprechungen überlegen ist.

Bedingungen

Stellen Sie sich vor, Sie holen Daten ab und bestimmen anhand der Nutzdaten , ob ein neuer API-Aufruf erforderlich ist. Sehen Sie sich den folgenden Code an, um zu sehen, wie dies über den Ansatz der „konventionellen Versprechungen“ erfolgt.

 const request = () => { return getData().then(data => { if (!data.car) { return fetchForCar(data.id).then(carData => { console.log(carData); return carData; }); } else { console.log(data); return data; } }); };

Wie Sie sehen können, sieht der obige Code bereits chaotisch aus, nur wegen einer zusätzlichen Bedingung. Async/await beinhaltet weniger Verschachtelung:

 const request = async () => { const data = await getData(); if (!data.car) { const carData = await fetchForCar(data); console.log(carData); return carData; } else { console.log(data); return data; } };

Fehlerbehandlung

Async/await gewährt Ihnen Zugriff, um sowohl synchrone als auch asynchrone Fehler in try/catch zu behandeln. Angenommen, Sie möchten JSON analysieren, das von einem asynchronen API-Aufruf stammt. Ein einziger Try/Catch könnte sowohl Parsing-Fehler als auch API-Fehler behandeln.

 const request = async () => { try { console.log(await getData()); } catch (err) { console.log(err); } };

Zwischenwerte

Was ist, wenn ein Versprechen ein Argument benötigt, das von einem anderen Versprechen gelöst werden sollte? Dies bedeutet, dass asynchrone Aufrufe nacheinander ausgeführt werden müssen.

Wenn Sie herkömmliche Versprechungen verwenden, erhalten Sie möglicherweise Code wie diesen:

 const request = () => { return fetchUserData() .then(userData => { return fetchCompanyData(userData); }) .then(companyData => { return fetchRetiringPlan(userData, companyData); }) .then(retiringPlan => { const retiringPlan = retiringPlan; }); };

Async/await glänzt in diesem Fall, wo verkettete asynchrone Aufrufe benötigt werden:

 const request = async () => { const userData = await fetchUserData(); const companyData = await fetchCompanyData(userData); const retiringPlan = await fetchRetiringPlan(userData, companyData); };

Asynchron parallel

Was ist, wenn Sie mehr als eine asynchrone Funktion parallel aufrufen möchten? Im folgenden Code warten wir auf die Auflösung von fetchHouseData und rufen dann fetchCarData auf. Obwohl diese voneinander unabhängig sind, werden sie sequentiell verarbeitet. Sie warten zwei Sekunden, bis beide APIs aufgelöst sind. Das ist nicht gut.

 function fetchHouseData() { return new Promise(resolve => setTimeout(() => resolve("Mansion"), 1000)); } function fetchCarData() { return new Promise(resolve => setTimeout(() => resolve("Ferrari"), 1000)); } async function action() { const house = await fetchHouseData(); // Wait one second const car = await fetchCarData(); // ...then wait another second. console.log(house, car, " in series"); } action();

Ein besserer Ansatz besteht darin, die asynchronen Aufrufe parallel zu verarbeiten. Überprüfen Sie den folgenden Code, um eine Vorstellung davon zu bekommen, wie dies in async/await erreicht wird.

 async function parallel() { houseDataPromise = fetchHouseData(); carDataPromise = fetchCarData(); const house = await houseDataPromise; // Wait one second for both const car = await carDataPromise; console.log(house, car, " in parallel"); } parallel();

Durch die parallele Verarbeitung dieser Anrufe müssen Sie für beide Anrufe nur eine Sekunde warten.

Neue Kernbibliotheksfunktionen

Node 8 bringt auch einige neue Kernfunktionen mit.

Dateien kopieren

Vor Node 8 haben wir zum Kopieren von Dateien zwei Streams erstellt und Daten von einem zum anderen geleitet. Der folgende Code zeigt, wie der Lesestrom Daten an den Schreibstrom weiterleitet. Wie Sie sehen können, ist der Code für eine so einfache Aktion wie das Kopieren einer Datei überladen.

 const fs = require('fs'); const rd = fs.createReadStream('sourceFile.txt'); rd.on('error', err => { console.log(err); }); const wr = fs.createWriteStream('target.txt'); wr.on('error', err => { console.log(err); }); wr.on('close', function(ex) { console.log('File Copied'); }); rd.pipe(wr);

In Node 8 sind fs.copyFile und fs.copyFileSync neue Ansätze zum Kopieren von Dateien mit viel weniger Aufwand.

 const fs = require("fs"); fs.copyFile("firstFile.txt", "secondFile.txt", err => { if (err) { console.log(err); } else { console.log("File copied"); } });

Promisify und Callbackify

util.promisify konvertiert eine reguläre Funktion in eine asynchrone Funktion. Beachten Sie, dass die eingegebene Funktion dem allgemeinen Callback-Stil von Node.js folgen sollte. Es sollte einen Rückruf als letztes Argument annehmen, dh (error, payload) => { ... } .

 const { promisify } = require('util'); const fs = require('fs'); const readFilePromisified = promisify(fs.readFile); const file_path = process.argv[2]; readFilePromisified(file_path) .then((text) => console.log(text)) .catch((err) => console.log(err));

Wie Sie sehen konnten, hat util.promisify fs.readFile in eine asynchrone Funktion konvertiert.

Auf der anderen Seite kommt Node.js mit util.callbackify . util.callbackify ist das Gegenteil von util.promisify : Es konvertiert eine asynchrone Funktion in eine Node.js-Funktion im Callback-Stil.

destroy -Funktion für Readables und Writeables

Die destroy Funktion in Node 8 ist eine dokumentierte Möglichkeit, einen lesbaren oder beschreibbaren Stream zu zerstören/zu schließen/abzubrechen:

 const fs = require('fs'); const file = fs.createWriteStream('./big.txt'); file.on('error', errors => { console.log(errors); }); file.write(`New text.\n`); file.destroy(['First Error', 'Second Error']);

Der obige Code führt zu einer Erstellung einer neuen Datei namens big.txt (falls noch nicht vorhanden) mit dem Text New text. .

Die Funktionen Readable.destroy und Writeable.destroy in Node 8 geben ein close -Ereignis und ein optionales error aus – destroy bedeutet nicht unbedingt, dass etwas schief gelaufen ist.

Spread-Operator

Der Spread-Operator (aka ... ) funktionierte in Node 6, aber nur mit Arrays und anderen iterablen:

 const arr1 = [1,2,3,4,5,6] const arr2 = [...arr1, 9] console.log(arr2) // expected output: [1,2,3,4,5,6,9]

In Node 8 können Objekte auch den Spread-Operator verwenden:

 const userCarData = { type: 'ferrari', color: 'red' }; const userSettingsData = { lastLoggedIn: '12/03/2019', featuresPlan: 'premium' }; const userData = { ...userCarData, name: 'Youssef', ...userSettingsData }; console.log(userData); /* Expected output: { type: 'ferrari', color: 'red', name: 'Youssef', lastLoggedIn: '12/03/2019', featuresPlan: 'premium' } */

Experimentelle Funktionen in Node 8 LTS

Experimentelle Funktionen sind nicht stabil, könnten veraltet sein und mit der Zeit aktualisiert werden. Verwenden Sie keine dieser Funktionen in der Produktion , bis sie stabil sind.

Asynchrone Hooks

Asynchrone Hooks verfolgen die Lebensdauer von asynchronen Ressourcen, die innerhalb von Node über eine API erstellt wurden.

Stellen Sie sicher, dass Sie die Ereignisschleife verstehen, bevor Sie mit asynchronen Hooks fortfahren. Dieses Video könnte helfen. Asynchrone Hooks sind nützlich zum Debuggen von asynchronen Funktionen. Sie haben mehrere Anwendungen; Eine davon sind Fehler-Stack-Traces für asynchrone Funktionen.

Schauen Sie sich den Code unten an. Beachten Sie, dass console.log eine asynchrone Funktion ist. Daher kann es nicht innerhalb von asynchronen Hooks verwendet werden. Stattdessen wird fs.writeSync verwendet.

 const asyncHooks = require('async_hooks'); const fs = require('fs'); const init = (asyncId, type, triggerId) => fs.writeSync(1, `${type} \n`); const asyncHook = asyncHooks.createHook({ init }); asyncHook.enable();

Sehen Sie sich dieses Video an, um mehr über asynchrone Hooks zu erfahren. Speziell in Bezug auf einen Node.js-Leitfaden hilft dieser Artikel dabei, asynchrone Hooks durch eine anschauliche Anwendung zu entmystifizieren.

ES6-Module in Knoten 8

Node 8 unterstützt jetzt ES6-Module, sodass Sie diese Syntax verwenden können:

 import { UtilityService } from './utility_service';

Um ES6-Module in Node 8 zu verwenden, müssen Sie Folgendes tun.

  1. Fügen Sie der Befehlszeile das Flag --experimental-modules hinzu
  2. Benennen Sie Dateierweiterungen von .js in .mjs um

HTTP/2

HTTP/2 ist das neueste Update des nicht oft aktualisierten HTTP-Protokolls, und Node 8.4+ unterstützt es nativ im experimentellen Modus. Es ist schneller, sicherer und effizienter als sein Vorgänger HTTP/1.1. Und Google empfiehlt, dass Sie es verwenden. Aber was macht es sonst noch?

Multiplexing

In HTTP/1.1 konnte der Server jeweils nur eine Antwort pro Verbindung senden. Bei HTTP/2 kann der Server mehr als eine Antwort parallel senden.

Server-Push

Der Server kann mehrere Antworten für eine einzelne Client-Anforderung pushen. Warum ist das vorteilhaft? Nehmen Sie als Beispiel eine Webanwendung. Konventionell,

  1. Der Client fordert ein HTML-Dokument an.
  2. Der Client entdeckt benötigte Ressourcen aus dem HTML-Dokument.
  3. Der Client sendet eine HTTP-Anforderung für jede erforderliche Ressource. Beispielsweise sendet der Client eine HTTP-Anforderung für jede im Dokument erwähnte JS- und CSS-Ressource.

Die Server-Push-Funktion macht sich die Tatsache zunutze, dass der Server all diese Ressourcen bereits kennt. Der Server überträgt diese Ressourcen an den Client. Für das Webanwendungsbeispiel pusht der Server also alle Ressourcen, nachdem der Client das ursprüngliche Dokument angefordert hat. Dadurch wird die Latenz reduziert.

Priorisierung

Der Client kann ein Priorisierungsschema festlegen, um zu bestimmen, wie wichtig jede erforderliche Antwort ist. Der Server kann dieses Schema dann verwenden, um die Zuweisung von Arbeitsspeicher, CPU, Bandbreite und anderen Ressourcen zu priorisieren.

Alte schlechte Gewohnheiten ablegen

Da HTTP/1.1 kein Multiplexing zuließ, werden mehrere Optimierungen und Problemumgehungen verwendet, um die langsame Geschwindigkeit und das Laden von Dateien zu verschleiern. Leider verursachen diese Techniken einen erhöhten RAM-Verbrauch und verzögertes Rendern:

  • Domain-Sharding: Es wurden mehrere Subdomains verwendet, sodass Verbindungen verteilt und parallel verarbeitet werden.
  • Kombinieren von CSS- und JavaScript-Dateien, um die Anzahl der Anfragen zu reduzieren.
  • Sprite-Maps: Kombinieren von Bilddateien, um HTTP-Anfragen zu reduzieren.
  • Inlining: CSS und JavaScript werden direkt im HTML platziert, um die Anzahl der Verbindungen zu reduzieren.

Mit HTTP/2 können Sie diese Techniken jetzt vergessen und sich auf Ihren Code konzentrieren.

Aber wie verwendet man HTTP/2?

Die meisten Browser unterstützen HTTP/2 nur über eine gesicherte SSL-Verbindung. Dieser Artikel kann Ihnen dabei helfen, ein selbstsigniertes Zertifikat zu konfigurieren. Fügen Sie die generierte .crt -Datei und die .key -Datei in einem Verzeichnis namens ssl . Fügen Sie dann den folgenden Code zu einer Datei namens server.js .

Denken Sie daran, das --expose-http2 in der Befehlszeile zu verwenden, um diese Funktion zu aktivieren. Das heißt, der Ausführungsbefehl für unser Beispiel lautet node server.js --expose-http2 .

 const http2 = require('http2'); const path = require('path'); const fs = require('fs'); const PORT = 3000; const secureServerOptions = { cert: fs.readFileSync(path.join(__dirname, './ssl/server.crt')), key: fs.readFileSync(path.join(__dirname, './ssl/server.key')) }; const server = http2.createSecureServer(secureServerOptions, (req, res) => { res.statusCode = 200; res.end('Hello from Toptal'); }); server.listen( PORT, err => err ? console.error(err) : console.log(`Server listening to port ${PORT}`) );

Natürlich unterstützen Node 8, Node 9, Node 10 usw. immer noch das alte HTTP 1.1 – die offizielle Node.js-Dokumentation zu einer Standard-HTTP-Transaktion wird noch lange nicht veraltet sein. Wenn Sie jedoch HTTP/2 verwenden möchten, können Sie mit diesem Node.js-Leitfaden tiefer gehen.

Sollte ich also am Ende Node.js 8 verwenden?

Node 8 kam mit Leistungsverbesserungen und neuen Funktionen wie async/await, HTTP/2 und anderen. End-to-End-Experimente haben gezeigt, dass Node 8 etwa 25 % schneller ist als Node 6. Dies führt zu erheblichen Kosteneinsparungen. Also für Greenfield-Projekte, unbedingt! Aber sollten Sie Node für bestehende Projekte aktualisieren?

Es hängt davon ab, ob Sie einen Großteil Ihres vorhandenen Codes ändern müssen. Dieses Dokument listet alle Node 8 Breaking Changes auf, wenn Sie von Node 6 kommen. Denken Sie daran, allgemeine Probleme zu vermeiden, indem Sie alle npm -Pakete Ihres Projekts mit der neuesten Node 8-Version neu installieren. Verwenden Sie außerdem auf Entwicklungscomputern immer dieselbe Node.js-Version wie auf Produktionsservern. Viel Glück!

Verwandt:
  • Warum zum Teufel sollte ich Node.js verwenden? Ein Fall-für-Fall-Tutorial
  • Debuggen von Speicherlecks in Node.js-Anwendungen
  • Erstellen einer sicheren REST-API in Node.js
  • Kabinenfieber-Codierung: Ein Node.js-Back-End-Tutorial