Warum Node.js wichtig ist
Node.js ist eine Runtime, die JavaScript außerhalb des Browsers ausführt. Es wurde 2009 veröffentlicht, nutzt die V8-Engine von Chrome und kombiniert diese mit APIs für Dateien, Netzwerke und Prozesse. Dadurch können Entwickler eine einzige Sprache über den gesamten Stack hinweg einsetzen.
Das Hauptmerkmal ist das nicht-blockierende, eventgesteuerte I/O. Anstatt für jede Anfrage einen Thread zu reservieren und zu warten, bis die Festplatte oder das Netzwerk antwortet, delegiert Node die langsame Operation und macht mit anderen Aufgaben weiter. Sobald die Operation abgeschlossen ist, wird ein Callback ausgeführt. Dieses Modell macht Node.js hocheffizient für APIs, Echtzeit-Apps und Tools – es ist derselbe Event Loop, den Sie bereits aus JavaScript im Browser kennen.
JavaScript auf dem Server ausführen
Node bietet dir ein globales process, ein Modulsystem und eine Standardbibliothek. Ein minimaler HTTP-Server benötigt keinerlei Abhängigkeiten.
// server.js
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.url === "/health") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
res.writeHead(404).end("Not found");
});
server.listen(process.env.PORT ?? 3000);
Die integrierten Imports mit dem Präfix node: machen deutlich, welche Module direkt von Node stammen. Dieser Server verarbeitet Anfragen, sobald sie eingehen, und blockiert niemals bei I/O-Operationen.
Module
Modernes Node verwendet ES modules, dieselbe import- und export-Syntax wie im Browser.
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from "./math.js";
Setze "type": "module" in der package.json, um .js-Dateien als ES modules zu behandeln. Node unterstützt weiterhin CommonJS (require und module.exports), auf das du in älterem Code und vielen Packages stoßen wirst. Neue Projekte sollten ES modules verwenden.
Das Dateisystem
Die Dateisystem-API von Node.js ist in asynchronen und synchronen Varianten verfügbar. Verwenden Sie in Servern die asynchrone API.
// files.js
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const file = path.join(import.meta.dirname, "data.json");
const data = JSON.parse(await readFile(file, "utf8"));
await writeFile(file, JSON.stringify({ ...data, updated: Date.now() }, null, 2));
Das node:path Modul übernimmt das plattformübergreifende Zusammenfügen von Pfaden, und import.meta.dirname gibt das Verzeichnis der aktuellen Datei zurück. Synchrone Funktionen wie readFileSync existieren und sind in Skripten sowie im Startup-Code völlig in Ordnung, sollten jedoch niemals innerhalb eines Request-Handlers verwendet werden.
Umgebung und Konfiguration
Konfigurationen gehören in Umgebungsvariablen, nicht in den Code.
// env.js
const config = {
port: Number(process.env.PORT ?? 3000),
databaseUrl: process.env.DATABASE_URL,
nodeEnv: process.env.NODE_ENV ?? "development",
};
Dadurch bleiben Secrets aus dem Repository fern und derselbe Build kann in der Entwicklung, im Staging und in der Produktion ausgeführt werden. Laden Sie lokale Werte in der Entwicklung aus einer .env-Datei mit einem Tool wie dotenv oder dem integrierten --env-file von Node.js und committen Sie diese Datei niemals.
APIs erstellen
Die meisten Node-Apps nutzen ein Framework für das Routing, Parsing und die middleware. Express ist das am weitesten verbreitete; Fastify ist schneller und schema-orientiert.
// api.js
import express from "express";
const app = express();
app.use(express.json());
app.get("/api/users", async (req, res, next) => {
try {
const users = await db.users.findMany();
res.json(users);
} catch (error) {
next(error);
}
});
app.post("/api/users", async (req, res, next) => {
try {
const user = await db.users.create(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
app.use((error, req, res, next) => {
console.error(error);
res.status(500).json({ message: "Something went wrong" });
});
app.listen(config.port);
Route-Handler sind async-Funktionen, Fehler werden an die middleware weitergeleitet und ein finaler Error-Handler wandelt Exceptions in Responses um. Geben Sie aussagekräftige Status-Codes und Header zurück und lesen Sie unter Web Security mehr über die Validierung von Inputs und den Schutz von Sessions.
Streams, Buffer und Events
Node verarbeitet Daten als Streams, sodass große Payloads nicht vollständig in den Speicher passen müssen.
// stream.js
import { createReadStream } from "node:fs";
import { createServer } from "node:http";
createServer((req, res) => {
createReadStream("movie.mp4").pipe(res);
}).listen(3000);
Ein Buffer enthält rohe Binärdaten, und ein Event Emitter ermöglicht es Objekten, Events zu veröffentlichen, die von anderen abonniert werden können. Streams und Events sind die Low-Level-Mechanismen hinter einem Großteil der Node-Standardbibliothek. Wenn man diese versteht, wird klar, wie Dateien, Sockets und HTTP unter der Haube funktionieren.
Fehler und Prozesssignale
Nicht abgefangene Fehler können einen Node-Prozess zum Absturz bringen, daher sollten diese bewusst behandelt werden.
// signals.js
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});
process.on("unhandledRejection", (reason) => {
console.error("Unhandled rejection", reason);
});
SIGTERM wird von den meisten Plattformen gesendet, wenn ein Container oder Prozess gestoppt werden soll; wenn Sie dieses Signal behandeln, können Sie laufende Anfragen abschließen, bevor das Programm beendet wird. Globale Handler dienen als Sicherheitsnetz für das Logging und sind kein Ersatz für die Fehlerbehandlung an der Stelle, an der sie auftreten.
Best Practices
- Verwende async APIs in Request-Handlern und blockiere niemals den Event Loop.
- Speichere Konfigurationen in Umgebungsvariablen.
- Validiere und bereinige alle Eingaben auf dem Server.
- Nutze ES-Module und das
node:-Präfix für Built-ins. - Zentralisiere das Error Handling in einer middleware.
- Streame große Dateien, anstatt sie vollständig in den Speicher zu laden.
- Behandle
SIGTERMfür einen Graceful Shutdown und stelle einen Health-Endpoint bereit.
Häufige Fehler
- Verwendung von
readFileSyncinnerhalb eines Request-Handlers, was den Server blockiert. - Ausführung von CPU-intensiven Aufgaben im Main Thread.
- Committen von
.env-Dateien oder Hardcoden von Secrets. - Vertrauen auf Client-Inputs ohne vorherige Validierung.
- Ignorieren von unhandled promise rejections.
- Einlesen großer Dateien vollständig in den Speicher anstatt sie zu streamen.
Wie geht es weiter?
Node.js vervollständigt das Full-Stack-Bild. Verwalte Abhängigkeiten mit npm, verstehe das HTTP-Protokoll, das dein Server spricht, sichere ihn mit Web Security ab und packe ihn mit Docker ein. Baue anschließend eine kleine API mit zwei Routes, einem Datenbankaufruf und einem Error-Handler und deploye diese.