Probiert aus, macht Fehler – darin findet ihr immer Perlen

Was ich aus drei gescheiterten WebSocket-Lösungen für TYPO3 gelernt habe

Es gibt keine Probleme. Nur Herausforderungen.

Christian Keuerleber
Frontend Usability ist sein Metier: mit SCSS und modernem JavaScript macht er jede Webanwendung fluffig
Lesedauer: ca. 5 Minuten

"Probiert aus. Macht Fehler. Darin könnt ihr immer Perlen finden." 

Das war die Kernbotschaft meines Vortrags *„The Response is Now – WebSockets with TYPO3 Restrictions"*. Und ich meine das wörtlich: Die eigentliche Geschichte dahinter ist keine glatte Erfolgsstory, sondern eine über vier Jahre und mehrere Anläufe, von denen die meisten erst einmal scheiterten. Der entscheidende Punkt dabei: Die finale Lösung ist kein Neuanfang, sondern baut direkt auf dem allerersten Versuch auf und erweitert ihn – ohne diesen ersten, noch unfertigen Wurf wäre sie nie entstanden. In diesem Artikel erzähle ich, wie das gelaufen ist – und warum ich glaube, dass sich das Ausprobieren und Scheitern immer lohnt.

Das Problem: Polling ist Verschwendung

Der Ausgangspunkt war simpel und dürfte vielen bekannt vorkommen: Ein Frontend soll sofort reagieren, wenn sich im Backend etwas ändert – neue Nachricht, neuer Task, neues Event. Die naheliegende Lösung ist Polling: Der Browser fragt in regelmäßigen Abständen beim Server nach, ob es etwas Neues gibt, der Server antwortet, das Frontend reagiert. Funktioniert, ist aber ineffizient – gerade in einer TYPO3/PHP-Umgebung, wo jede Anfrage einen vollen Request-Response-Zyklus bedeutet, egal ob es tatsächlich etwas Neues gibt oder nicht.

WebSockets lösen das grundsätzlich anders: Statt immer wieder neu zu fragen, hält der Browser eine dauerhafte, bidirektionale Verbindung zum Server. Daten können übertragen werden, sobald sie anfallen – in beide Richtungen, ohne dass jemand fragen muss. Die Idee ist alt und die Technik ausgereift. Das Problem war nie das Konzept, sondern die Frage: Wie bekommt man das sauber mit TYPO3 und PHP zusammen?

Perle 1: Der PoC von 2021

Meine erste Antwort auf diese Frage war ein Proof of Concept aus dem Jahr 2021 – und ehrlich gesagt keine besonders schöne. Die Implementierung des WebSocket-Gateways war, um es milde auszudrücken, unschön: Für jeden Server-zu-Server-Aufruf brauchte es eine eigene Route, und das Ganze konnte im Kern nur eine einzige Client-Verbindung gleichzeitig verwalten – nie mehr als eine wurde tatsächlich gespeichert. Für ein System, das mehrere gleichzeitig verbundene Nutzer bedienen soll, ist das offensichtlich unbrauchbar. Der Kunde zeigte damals wenig Interesse an dem Konzept, und das Projekt verschwand erst einmal in der Schublade – aber eben nicht für immer, wie sich später zeigen sollte.

Die Perle daraus: Ein PoC muss nicht schön sein, er muss nur zeigen, wo die eigentlichen Probleme liegen und ob das Grundprinzip trägt. Genau dieses Grundprinzip – ein Gateway, das Verbindungen entgegennimmt und Events verteilt – war hier schon richtig erkannt. Was fehlte, war eine Architektur, die von Grund auf mit mehreren Verbindungen umgehen kann.

Perle 2: ReactPHP und die Grenzen der Hilfsmittel

Erst 2025, mehrere Jahre später, griff ich das Thema wieder auf. Der nächste Anlauf setzte auf ReactPHP. Auf dem Papier eine naheliegende Wahl für asynchrones PHP. In der Praxis funktionierten die WebSocket-Funktionen nicht wie erwartet, und das Routing für POST-Aufrufe wollte einfach nicht laufen. An dieser Stelle habe ich auch KI-Unterstützung zurate gezogen – mit gemischtem Ergebnis: 

Der Assistent erfand munter Methoden, die es in der Bibliothek schlicht nicht gab.

Eine gute Erinnerung daran, dass man jede vorgeschlagene Lösung selbst verifizieren muss, so überzeugend sie auch klingt.


 

Die Perle daraus: Nicht jedes Werkzeug, das auf dem Papier passt, passt auch in der Praxis zum eigenen Stack – und weder eine Bibliothek noch ein Sprachmodell ersetzen das eigene Nachprüfen.

Perle 3: RatchetPHP und der Proxy-Konflikt

 

RatchetPHP war der nächste Versuch – mit saubereren Interfaces und deutlich besserer Dokumentation als ReactPHP. Diesmal lag das Problem woanders, und zwar an einer Stelle, die ich vorher nicht auf dem Schirm hatte: Ein zweiter PHP-Server ließ sich nicht ohne Konflikte gemeinsam mit React über nginx proxien. 

Die Architektur war technisch stimmig, scheiterte aber am Deployment-Setup drumherum.

Die Perle daraus: Manchmal liegt das eigentliche Hindernis nicht im Code, sondern in der Infrastruktur, die man um den Code herum baut. Das gehört genauso zur Lösung wie die Kernlogik selbst.

Die Lösung: NestJS – und das Kobayashi-Maru-Prinzip

Am Ende war es dann kein PHP-Framework, das die Aufgabe gelöst hat, sondern NestJS. Und hier schließt sich der Kreis zum PoC von 2021: 

Die finale Architektur ist im Kern genau dessen Gateway-Idee, nur diesmal richtig zu Ende gedacht. Die Storage-Klasse, die jetzt beliebig viele Client-Verbindungen verwaltet, ist die direkte Antwort auf die eine große Schwäche des Ur-PoC, der nie mehr als eine Verbindung speichern konnte. 

Ohne den ersten, holprigen Versuch hätte ich vermutlich gar nicht gewusst, worauf es bei dieser Storage-Klasse wirklich ankommt – das Problem war ja schon vier Jahre vorher exakt benannt worden, nur eben noch nicht gelöst.

Die finale Architektur besteht aus drei Bausteinen: dem erwähnten, jetzt sauber gelösten WebSocket-Gateway als Interface zu den Standardbibliotheken, einer Controller-Struktur mit einer Methode pro Route – bewusst angelehnt an die Art, wie man APIs in TYPO3 aufbaut – und eben jener Storage-Klasse für die aktiven Client-Verbindungen. Wer aus der Extbase-Welt kommt, findet sich in dieser Struktur schnell zurecht, auch wenn die Sprache eine andere ist.

Genau das bringt mich zum Kern des Vortrags, den ich als eine Art Kobayashi-Maru-Prinzip bezeichnet habe – eine kleine Star-Trek-Anspielung, die ich kurz erklären will, falls sie nicht jedem geläufig ist. Der Kobayashi Maru ist in Star Trek eine Prüfungssimulation der Sternenflotten-Akademie: ein Szenario, das eigentlich unlösbar ist – jede Entscheidung führt zur Niederlage. Die bekannteste Figur, die den Test trotzdem „gewonnen" hat, ist James T. Kirk. Sein Trick dabei ist der entscheidende Punkt: Er hat nicht in der Prüfungssituation selbst improvisiert und gehofft, dass es schon klappen wird. Er hat vorher, in Ruhe, die Simulation umprogrammiert – und wusste dadurch bereits vor dem eigentlichen Test, dass sein Vorgehen funktioniert.


 

 

Genau darin liegt für mich die Parallele, und das ist auch der Kernsatz, den ich aus dem Vortrag mitgeben wollte: 

Eine Lösung ist dann die beste, wenn man vorher schon weiß, dass sie funktioniert. 

Bei Kirk war das die vorherige Programmierarbeit, bei uns war es der PoC. Der PoC von 2021 war zwar für sich genommen nicht einsatzfähig, aber er hat vorab bewiesen, dass das Grundprinzip – ein Gateway, das Verbindungen entgegennimmt und Events verteilt – tatsächlich funktioniert. Als ich 2025 bei NestJS ankam, war das deshalb kein Sprung ins Ungewisse mehr, sondern die Umsetzung von etwas, von dem ich durch den PoC schon wusste, dass es trägt. Daher das Fazit, das ich dem Vortrag gegeben habe:

"The best solution is the one you know that already works."

Nicht die theoretisch eleganteste Lösung gewinnt, sondern die, von der man aus eigener Erfahrung – und sei es aus einem holprigen Vorläufer – bereits weiß, dass sie funktioniert.

Sicherheit darf dabei nicht hinten runterfallen

Ein System, das Server-zu-Server- und Server-zu-Client-Kommunikation offenlegt, braucht klare Grenzen. Die Server-zu-Server-Kommunikation läuft über einen gemeinsamen X-Websocket-Token im HTTP-Header, zusätzlich wird die IP-Adresse gegen den TYPO3-Server abgeglichen. Auf der Client-Seite gilt ein einfaches, aber wichtiges Prinzip: Der Client bekommt nie Rohdaten direkt über den WebSocket geliefert, sondern ausschließlich Benachrichtigungen darüber, dass es etwas Neues gibt. Die eigentlichen Inhalte lädt er anschließend ganz regulär über die UID nach – und durchläuft dabei die bestehenden ACL-Prüfungen von TYPO3. Fehlt die Berechtigung, gibt es einen 403, wie überall sonst auch.

Ein typischer Ablauf sieht dann so aus:

```
// Client → Server (Login/Identität) {"event":"identity", "data":{"access":[1,2], "user":2, "company":3}}

// Server → Server (neuer Task) {"type":"task", "access":"2", "company":"3", "uid":"6"}

// Server → Client (Benachrichtigung, keine Daten) {"action":"newEvent", "type":"task", "uid":6}
```

Die eigentliche Zugriffskontrolle bleibt damit vollständig dort, wo sie hingehört: in TYPO3.

Fazit

Vier Anläufe über vier Jahre, drei davon gescheitert – und trotzdem war jeder einzelne notwendig, um am Ende bei einer Lösung zu landen, die tatsächlich trägt. Der PoC von 2021 legte das Fundament und benannte das eigentliche Problem, auch wenn er es selbst noch nicht lösen konnte. ReactPHP zeigte die Grenzen blinden Vertrauens in Bibliotheken und KI-Vorschläge, RatchetPHP zeigte, dass Infrastruktur genauso zählt wie Code. 

 

Die finale NestJS-Lösung ist am Ende keine davon losgelöst entstanden, sondern eine direkte Weiterentwicklung jenes ersten, unfertigen PoC – ohne ihn gäbe es sie so nicht. Deshalb bleibe ich bei meinem Fazit aus dem Vortrag:

Probiert aus. Macht Fehler. Darin könnt ihr immer Perlen finden. Die beste Lösung ist am Ende oft nicht die theoretisch schönste, sondern die, die man durch genau diesen Prozess wirklich verstanden hat.

Teilen:

Weitere Beiträge

Code gestaltet Zukunft – Unsere Leidenschaft für Innovation
Fahim Nasirzadeh, Entwicklung bei punkt.de
Arbeiten bei punkt.de