The user presses send and this sends a POST request with the content of the message to the API. The API processes this message, stores it in the database and puts in a queue a message with this content:
{"user-id": "42", "sender": "9", "content": "new-message"}.
If the queue is then processed by supervisor (process that is always kept running), a message is sent to the NodeJS server.
This in turn sends a push message trough a websocket to the browser of just this one user (42). When that has arrived, that user's browser asks the API to retrieve "new-message" messages in the conversation with user 9.
Because only a new-message is forwarded, and the content of that message itself has to be retrieved separately, all this remains secure. So the socket only gets information about new messages and the content of that message remains behind the regular API.
By using already existing packages, this is easy to implement. We did not have to write our own code for the NodeJS part, but were able to build completely on the existing package. For the part in Laravel we wrote a custom broadcast service and a custom notification class.
On the React side of the application we can use an already existing socket.io implementation, so also for this we could rely on an already existing code.