Zum Inhalt springen
Deutsch

Express-Backend

ExpressBackend erlaubt dir, actor-ts-Routen durch Express laufen zu lassen — das am häufigsten genutzte Node-HTTP- Framework. Die richtige Wahl, wenn:

  • Du bereits in Express-Middleware investiert hast (eigene Auth, Session-Handling, app-spezifische Instrumentierung).
  • Die Vertrautheit deines Teams mit Express größer ist als die Framework-Vorteile von Fastify.
  • Du eine bestehende Express-App inkrementell zu actor-ts migrierst.
import { ActorSystem } from 'actor-ts';
import { HttpExtensionId } from 'actor-ts/http';
import { ExpressBackend, ExpressBackendOptions } from 'actor-ts/http';
const http = system.extension(HttpExtensionId);
await http.newServerAt('0.0.0.0', 8080)
.useBackend(new ExpressBackend())
.bind(routes);
const expressBackendOptions = ExpressBackendOptions.create().withMaxBodyBytes(4 * 1024 * 1024);
new ExpressBackend(
expressBackendOptions,
);

Settings im Express-Stil. maxBodyBytes steht per Default auf 1 MiB, dem gemeinsamen Cap aller Backends — heb ihn nur für einen Endpunkt an, der wirklich mehr annimmt, so wie oben.

req.ip ist der Socket-Peer, und der landet in HttpRequest.remoteAddress. Express kann angewiesen werden, ihn stattdessen aus X-Forwarded-For abzuleiten — aber nur auf einer eigenen App (app.set(...), via .withApp(app) übergeben), denn ExpressBackendOptions hat kein trustProxy-Feld.

Nimm dafür lieber trustedProxies von IpAllowlist. Das ist dieselbe Trust-by-Address-Regel, braucht keine eigene App und liest sich auf allen drei Backends gleich — Hono hat überhaupt kein Trust-Proxy-Konzept.

Jede Response, die dieses Backend schreibt, trägt X-Content-Type-Options: nosniff — inklusive seines Fehler-Mappings, des fallback-404 und des 413 bei zu großem Body, die keine Middleware sieht. Ein Header aus der Response selbst gewinnt weiterhin.

Konfiguriert wird das pro Server, nicht pro Backend — eine Oberfläche statt dreier:

await http.newServerAt('0.0.0.0', 8080)
.useBackend(new ExpressBackend())
.withSecurityHeaders(false) // oder ein SecurityHeadersOptions-Bündel
.bind(routes);

Siehe Sicherheit.

import express from 'express';
import { ExpressBackend } from 'actor-ts/http';
const backend = new ExpressBackend();
await http.newServerAt('0.0.0.0', 8080)
.useBackend(backend)
.bind(routes);
// Zugriff auf die rohe Express-App:
backend.getApp().use(express.session({ secret: '...' }));
backend.getApp().use(expressRateLimitFromNpm);
backend.getApp().use(customAuth);

Express-Middleware umhüllt die actor-ts-Routen — der Request fließt zuerst durch deine Middleware, dann zum actor-ts-Handler.

Das ist der Hauptgrund, Express statt Fastify zu nehmen: das Middleware-Ökosystem. Wenn du es nicht brauchst, ist Fastify schneller.

Eine websocket()-Route wird als ganz normales Express-GET registriert, und das Upgrade wird durch die App dispatcht — also läuft app.use(...) beim Handshake genau so wie bei einem Request:

const backend = new ExpressBackend();
backend.getApp().use(customAuth); // gates /ws as well
await http.newServerAt('0.0.0.0', 8080)
.useBackend(backend)
.bind(websocket('/ws', chat));

Eine Middleware, die den Request beantwortet (res.status(401).end()), bricht den Handshake ab: der Socket wird geschlossen statt hochgestuft, und onClientConnected feuert nie. Eine Middleware, die next() aufruft, lässt ihn zum Upgrade-Guard des Frameworks durch (withMiddleware() / allowedOrigins), der weiterhin das letzte Wort hat — siehe WebSockets.

Zwei Einschränkungen auf diesem Pfad. Eine Middleware, die weder antwortet noch next() aufruft, lässt den Client warten — genauso, wie sie einen normalen Request hängen lassen würde. Und der Body einer Ablehnung erreicht den Client nur auf Runtimes, die das Schreiben auf einen übernommenen Upgrade-Socket zulassen: Bun und Node tun das, Deno nicht — dort sieht der Client stattdessen den Verbindungsabbruch. Abgelehnt wird der Handshake in beiden Fällen.

import express from 'express';
import https from 'node:https';
// TLS wird auf einer eigenen Express-App eingerichtet, in Nodes
// `https`-Server eingewickelt; die App via `.withApp(app)` übergeben.
const app = express();
https.createServer(
{
cert: fs.readFileSync('./tls/cert.pem'),
key: fs.readFileSync('./tls/key.pem'),
},
app,
);
const expressBackendOptions = ExpressBackendOptions.create().withApp(app);
new ExpressBackend(expressBackendOptions);

Gestützt auf das https-Modul von Node. Dieselben Vorbehalte wie beim Fastify-Backend — typischerweise terminiert TLS am Load-Balancer.

Terminal-Fenster
npm install express
# oder: bun add express

Express 5+ empfohlen; ältere Versionen können funktionieren, sind aber nicht getestet.

Grobe Zahlen:

  • 40K–60K req/sec für triviale Routen (langsamer als Fastify).
  • P50-Latenz ähnlich; der Durchsatz unterscheidet sich.

Die Middleware-Kette von Express hat mehr Overhead als Fastifys Hooks. Für High-Throughput-Pfade bevorzuge Fastify; für Pfade, die durch schwere Middleware ausgebremst werden, spielt die Framework-Wahl kaum eine Rolle.

Wenn du eine bestehende Express-App hast und actor-ts hinzufügen willst:

import express from 'express';
import { ExpressBackend, ExpressBackendOptions } from 'actor-ts/http';
const app = express();
// Bestehende Routen + Middleware bleiben unverändert:
app.use('/legacy', oldLegacyRouter);
// Die bestehende App an das Backend übergeben; actor-ts registriert
// seine Routen auf derselben App, neben deinen bestehenden.
const expressBackendOptions = ExpressBackendOptions.create().withApp(app);
const backend = new ExpressBackend(expressBackendOptions);
await http.newServerAt('0.0.0.0', 8080)
.useBackend(backend)
.bind(routes);

Übergib deine bestehende App via .withApp(app) an das Backend — actor-ts registriert seine Routen auf derselben App, sodass deine bestehenden Routen und Middleware weiter funktionieren, ohne den ganzen HTTP-Stack auszutauschen.