Express.js: Das meistgenutzte Node.js-Framework für REST-APIs und Webserver

Express.js ist seit über einem Jahrzehnt das Rückgrat unzähliger Node.js-Projekte. Als minimales, unopinioniertes Web-Framework legt es keine starre Projektstruktur vor, bietet aber alle Bausteine, die für die Entwicklung von REST-APIs und Webservern benötigt werden. Dieser Artikel erklärt die wichtigsten Konzepte und zeigt, warum Express trotz jüngerer Konkurrenz nach wie vor relevant ist.

Was ist Express.js?

Express.js ist ein schlankes Web-Framework für Node.js, das erstmals 2010 veröffentlicht wurde. Es abstrahiert die rohe Node.js-HTTP-API und stellt eine einfachere Schnittstelle für Routing, Middleware-Verwaltung und Request-Handling bereit. Express selbst bringt kaum Funktionalität mit – stattdessen setzt es auf ein ökosystem von Middleware-Paketen, die je nach Bedarf eingebunden werden.

Dieses Prinzip macht Express extrem flexibel: Wer eine einfache statische Datei-Auslieferung benötigt, nutzt zwei Zeilen Code. Wer eine vollständige REST-API mit Authentifizierung, Datenbankzugriff und Validierung baut, kann die Architektur frei wählen. Die offizielle Dokumentation ist unter expressjs.com verfügbar.

Routing: GET, POST, PUT, DELETE

Routing in Express bedeutet, HTTP-Methoden und URL-Pfade mit Handler-Funktionen zu verbinden. Die Syntax ist dabei direkt und lesbar:

const express = require('express');
const app = express();

app.get('/users', (req, res) => {
  res.json({ users: [] });
});

app.post('/users', (req, res) => {
  // Neuen Nutzer anlegen
  res.status(201).json({ created: true });
});

app.put('/users/:id', (req, res) => {
  res.json({ updated: req.params.id });
});

app.delete('/users/:id', (req, res) => {
  res.status(204).send();
});

app.listen(3000);

URL-Parameter werden mit dem Doppelpunkt-Präfix definiert (z. B. :id) und über req.params ausgelesen. Query-Parameter stehen über req.query zur Verfügung. Express unterstützt außerdem reguläre Ausdrücke und Platzhaltermuster für komplexere URL-Strukturen.

Für größere Projekte empfiehlt sich die Nutzung von express.Router(), um Routen in separate Module aufzuteilen und nach Feature oder Ressource zu gruppieren.

Das Middleware-Konzept

Middleware ist das Herzstück von Express. Eine Middleware-Funktion hat Zugriff auf das Request-Objekt (req), das Response-Objekt (res) und die next-Funktion, die den Aufruf der nächsten Middleware in der Kette auslöst. Jede eingehende Anfrage durchläuft die registrierten Middlewares in der Reihenfolge ihrer Registrierung.

Typische Middleware-Aufgaben sind: Logging, Authentifizierungsprüfung, Request-Body-Parsing, CORS-Header-Setzung und Rate-Limiting. Middlewares können global (für alle Routen) oder lokal (nur für bestimmte Routen) registriert werden.

body-parser und CORS

Zwei der am häufigsten eingesetzten Middleware-Pakete sind body-parser und cors. Seit Express 4.16 ist body-parser für JSON und URL-encoded Bodies bereits integriert:

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

Für Cross-Origin-Anfragen wird das cors-Paket eingesetzt:

const cors = require('cors');
app.use(cors({ origin: 'https://meinefrontend.de' }));

Fehlerbehandlung mit Error-Handling-Middleware

Express unterscheidet reguläre Middleware (drei Parameter: req, res, next) von Error-Handling-Middleware (vier Parameter: err, req, res, next). Letztere fängt Fehler ab, die in vorherigen Middlewares oder Routen mit next(err) weitergereicht wurden:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: err.message || 'Interner Serverfehler'
  });
});

Wichtig: Die Error-Handling-Middleware muss als letztes registriert werden, nach allen Routen und normalen Middlewares.

Tipp: Nutze asynchrone Routen-Handler mit einem Try-Catch-Wrapper oder einer Hilfsfunktion wie express-async-errors, um unbehandelte Promise-Rejections automatisch an die Error-Handling-Middleware weiterzuleiten. Ab Express 5 (aktuell im Release-Candidate-Stadium) werden async-Fehler automatisch abgefangen.

Statische Dateien ausliefern

Express kann mit wenigen Zeilen als statischer Dateiserver fungieren:

app.use(express.static('public'));

Alle Dateien im Verzeichnis public werden damit direkt unter dem Root-Pfad erreichbar. Das eignet sich für einfache Single-Page-Applications oder für die Auslieferung von Assets wie Bilder, CSS und JavaScript-Bundles.

Typische REST-API-Struktur

Eine gut organisierte Express-API folgt üblicherweise dieser Ordnerstruktur:

src/
  routes/        ← Router-Definitionen pro Ressource
  controllers/   ← Request-Handling-Logik
  services/      ← Geschäftslogik und Datenbankzugriff
  middleware/    ← Eigene Middlewares (auth, validation)
  app.js         ← Express-App-Konfiguration
  server.js      ← HTTP-Server-Start

Diese Trennung sorgt für Wartbarkeit und Testbarkeit. Controller-Funktionen delegieren Logik an Services, was Unit-Tests ohne HTTP-Layer ermöglicht.

Achtung: Express wird seit einigen Jahren nicht mehr aktiv von einem dedizierten Core-Team weiterentwickelt. Express 5 befindet sich seit Jahren im Beta-Stadium. Für leistungskritische Anwendungen oder neue Projekte sollte die Entscheidung zwischen Express und moderneren Alternativen wie Fastify oder Hono bewusst getroffen werden.

Vergleich: Express.js vs. Fastify vs. Hono vs. Koa

Kriterium Express.js Fastify Hono Koa
Requests/Sekunde (grob) ~50.000 ~80.000 ~120.000+ ~55.000
TypeScript-Support Via @types/express Eingebaut Eingebaut (first-class) Via @types/koa
Bundle-Größe ~208 KB ~1,1 MB ~15 KB ~26 KB
Ökosystem / Middleware Sehr groß Groß (Plugins) Wachsend Mittel
Schema-Validierung Externe Pakete (joi, zod) Eingebaut (JSON Schema) Via Zod/Valibot Externe Pakete
Edge-Runtime-fähig Nein Nein Ja (Cloudflare, Deno, Bun) Nein

Eine detaillierte Benchmark-Übersicht stellt das Fastify-Projekt regelmäßig bereit. Hono ist besonders für Edge-Deployments interessant, da es auf der Web-Standard-API (Request/Response) aufbaut.

Häufig gestellte Fragen

Ist Express.js für Produktionsumgebungen geeignet?
Ja, Express läuft in zahlreichen großen Produktionssystemen. Für einfache bis mittlere APIs ist es nach wie vor eine solide Wahl. Bei sehr hohen Anforderungen an Durchsatz oder strikter Typsicherheit bieten Fastify oder NestJS Vorteile.
Kann ich Express.js mit TypeScript verwenden?
Ja. Mit dem Paket @types/express aus DefinitelyTyped stehen vollständige Typdefinitionen zur Verfügung. Alternativ kann Express zusammen mit ts-node oder einem Build-Schritt mit tsc eingesetzt werden.
Was ist der Unterschied zwischen Express.js und Koa?
Koa wurde von den ursprünglichen Express-Entwicklern als modernerer Nachfolger konzipiert. Es verwendet async/await-Middleware statt des Callback-basierten Ansatzes von Express und hat einen kleineren Core. Das Ökosystem von Koa ist jedoch deutlich kleiner.
Wie sichere ich eine Express-API ab?
Grundlegende Sicherheitsmaßnahmen umfassen: Helmet.js für HTTP-Sicherheits-Header, Rate-Limiting mit express-rate-limit, Eingabevalidierung mit joi oder zod, HTTPS-Erzwingung und den Einsatz von JWT oder Session-basierter Authentifizierung. Das npm-Paket helmet sollte in keiner Express-App fehlen.