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.

Lesezeit 8 Min. Aktualisiert 28.05.2026 2 Quellen Mateusz Viola Mateusz Viola
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)
Mateusz Viola

Ü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

TypeScript Playground nutzen

Sofort im Browser, ohne Anmeldung.

Zum Playground