Praxis
TypeScript serverseitig: Node.js und TypeScript produktiv einsetzen
Wie du TypeScript in einer Node.js-Umgebung einsetzt: Projektsetup, tsconfig für Server, Module, Typen für Node-APIs und den Einsatz mit Express oder Fastify.
Inhalt
TypeScript ist längst nicht nur für Frontend-Entwicklung geeignet. Im Backend-Bereich hat es sich ebenfalls als Standard etabliert. Frameworks wie NestJS, Fastify und viele Unternehmensprojekte mit Express setzen vollständig auf TypeScript. Dieser Ratgeber zeigt dir, wie du ein solides Node.js-TypeScript-Setup aufbaust.
Projektstruktur und initiales Setup
Beginne mit einem leeren Node.js-Projekt und installiere die notwendigen Abhängigkeiten:
// package.json (relevante Teile)
// npm init -y
// npm install typescript @types/node tsx --save-dev
// npm install express
// npm install @types/express --save-dev
Die @types-Pakete enthalten TypeScript-Typdefinitionen für JavaScript-Bibliotheken ohne eigene Typen. @types/node stellt Typen für alle eingebauten Node.js-Module bereit.
tsconfig.json für Node.js konfigurieren
Die tsconfig.json muss für serverseitigen Einsatz angepasst werden:
// tsconfig.json als TypeScript-Objekt zur Veranschaulichung
const tsconfigBeispiel = {
compilerOptions: {
target: "ES2022",
module: "NodeNext",
moduleResolution: "NodeNext",
outDir: "./dist",
rootDir: "./src",
strict: true,
esModuleInterop: true,
skipLibCheck: true,
declaration: true,
},
include: ["src/**/*"],
exclude: ["node_modules", "dist"],
};
target: "ES2022" ist ein guter Wert für aktuelle Node.js-Versionen (18 und höher). module: "NodeNext" aktiviert die korrekte ES-Modul-Auflösung.
Typisierter HTTP-Server mit Express
Ein einfacher Express-Server mit vollständiger Typisierung:
import express, { Request, Response, NextFunction } from "express";
interface Produkt {
id: number;
name: string;
preis: number;
lagerbestand: number;
}
const produkte: Produkt[] = [
{ id: 1, name: "Laptop", preis: 999.99, lagerbestand: 5 },
{ id: 2, name: "Maus", preis: 29.99, lagerbestand: 50 },
];
const app = express();
app.use(express.json());
app.get("/produkte", (_req: Request, res: Response) => {
res.json(produkte);
});
app.get("/produkte/:id", (req: Request<{ id: string }>, res: Response) => {
const id = parseInt(req.params.id, 10);
const produkt = produkte.find((p) => p.id === id);
if (!produkt) {
res.status(404).json({ fehler: "Produkt nicht gefunden" });
return;
}
res.json(produkt);
});
app.listen(3000, () => {
console.log("Server läuft auf Port 3000");
});
Der generische Parameter Request<\{ id: string \}> gibt TypeScript die Information, welche Route-Parameter erwartet werden. Das verhindert Tippfehler bei req.params.
Fehlerbehandlung mit typisiertem Middleware
Fehlerbehandlung in Express erfordert eine spezifische Signatur mit vier Parametern:
interface AppFehler extends Error {
statusCode?: number;
details?: string;
}
function fehlerHandler(
err: AppFehler,
_req: Request,
res: Response,
_next: NextFunction
): void {
const statusCode = err.statusCode ?? 500;
const nachricht = err.message ?? "Interner Serverfehler";
console.error(`Fehler ${statusCode}: ${nachricht}`);
res.status(statusCode).json({
fehler: nachricht,
details: err.details,
});
}
app.use(fehlerHandler);
Die erweiterte Error-Schnittstelle AppFehler erlaubt benutzerdefinierte Fehlerinformationen bei gleichzeitiger Kompatibilität mit dem Standard-Error-Typ.
Datenbankzugriff mit Typen
Bei Datenbankoperationen ist Typisierung besonders wertvoll. Hier ein Beispiel mit dem Node.js-Treiber für SQLite:
import Database from "better-sqlite3";
interface BenutzerZeile {
id: number;
name: string;
email: string;
erstellt_am: string;
}
class BenutzerRepository {
private db: Database.Database;
constructor(datenbankPfad: string) {
this.db = new Database(datenbankPfad);
}
alleBenutzer(): BenutzerZeile[] {
const anweisung = this.db.prepare("SELECT * FROM benutzer");
return anweisung.all() as BenutzerZeile[];
}
benutzerNachId(id: number): BenutzerZeile | undefined {
const anweisung = this.db.prepare("SELECT * FROM benutzer WHERE id = ?");
return anweisung.get(id) as BenutzerZeile | undefined;
}
}
Der Cast auf den spezifischen Typ ist notwendig, weil Datenbankabfragen zur Laufzeit einen unbekannten Typ zurückgeben. Für eine robustere Lösung empfehlen sich Bibliotheken wie Zod für die Laufzeit-Validierung.
Umgebungsvariablen typisieren
Umgebungsvariablen sind in Node.js standardmäßig untypisiert. Du kannst sie typsicher machen:
interface UmgebungsVariablen {
PORT: string;
DATABASE_URL: string;
JWT_SECRET: string;
NODE_ENV: "development" | "production" | "test";
}
function pruefeUmgebung(): UmgebungsVariablen {
const pflichtFelder: (keyof UmgebungsVariablen)[] = [
"DATABASE_URL",
"JWT_SECRET",
];
for (const feld of pflichtFelder) {
if (!process.env[feld]) {
throw new Error(`Pflicht-Umgebungsvariable fehlt: ${feld}`);
}
}
return process.env as unknown as UmgebungsVariablen;
}
const env = pruefeUmgebung();
const port = parseInt(env.PORT ?? "3000", 10);
Diese Funktion prüft beim Start der Anwendung, ob alle notwendigen Variablen gesetzt sind und gibt ein vollständig typisiertes Objekt zurück.
Build-Pipeline einrichten
Für den Produktiveinsatz brauchst du eine Build-Pipeline:
// package.json scripts (als TypeScript-Objekt dargestellt)
const scripts = {
build: "tsc --project tsconfig.json",
start: "node dist/index.js",
dev: "tsx watch src/index.ts",
"type-check": "tsc --noEmit",
};
tsx watch startet den Entwicklungsserver mit Hot-Reload. tsc --noEmit prüft Typen ohne Ausgabe und eignet sich für CI-Pipelines.
Fazit
TypeScript im Node.js-Backend bringt dieselben Vorteile wie im Frontend: Typfehler werden zur Kompilierzeit erkannt, Refactoring wird sicherer und IDEs können präzisere Vorschläge machen. Mit dem richtigen Setup aus tsconfig, Typen für externe Module und einer klaren Projektstruktur ist TypeScript serverseitig genauso produktiv wie clientseitig.
Häufige Fragen
Kann Node.js TypeScript direkt ohne Kompilierung ausführen?
Ab Node.js 22.6 gibt es experimentelle Unterstützung für TypeScript ohne vorherige Kompilierung via --experimental-strip-types. Für den Produktiveinsatz empfiehlt sich aber weiterhin eine Build-Pipeline mit tsc oder esbuild.
Was ist der Unterschied zwischen ts-node und tsx?
ts-node ist das klassische Tool zum direkten Ausführen von TypeScript in Node.js. tsx ist eine schnellere Alternative auf Basis von esbuild, die TypeScript und JSX ohne Typ-Prüfung transpiliert. Für schnelle Entwicklungsiterationen ist tsx vorzuziehen.
Soll ich CommonJS oder ES-Module in meinem Node.js-TypeScript-Projekt verwenden?
Neue Projekte sollten ES-Module verwenden. Stelle in tsconfig.json module auf 'NodeNext' oder 'ESNext' und in package.json type auf 'module'. Das ergibt besseres Tree-Shaking und modernere Syntax.
Quellen
- Node.js Dokumentation: TypeScript-Unterstützung (nodejs.org)
- TypeScript Handbook: Module Resolution (typescriptlang.org)
Über die Autorenschaft
Mateusz Viola
Betreiber und redaktionelle Verantwortung typescript-playground.de
Themengebiet: Mathematik, Kalenderrechnung, Schaltjahre, Statistik und ISO 8601
Mehr über Mateusz Viola →Verwandte Artikel
Grundlagen
Was ist TypeScript? Eine Einführung in die typisierte JavaScript-Erweiterung
TypeScript erweitert JavaScript um statische Typen und macht große Codebases wartbarer. Lerne die Grundlagen, die Geschichte und die wichtigsten Vorteile von TypeScript.
Lesezeit 7 Min.
Grundlagen
TypeScript vs. JavaScript: Die wichtigsten Unterschiede im Überblick
TypeScript und JavaScript unterscheiden sich grundlegend in der Typprüfung, Werkzeugunterstützung und Wartbarkeit. Dieser Ratgeber erklärt die Unterschiede und hilft bei der Wahl der richtigen Sprache.
Lesezeit 8 Min.
Typen
TypeScript Typen Grundlagen: string, number, boolean und mehr
Die primitiven Typen string, number und boolean sind das Fundament von TypeScript. Dieser Ratgeber erklärt alle grundlegenden Typen mit praktischen Beispielen.
Lesezeit 9 Min.