I'm implementing a real-time stock opname (inventory check) feature using Laravel and Ratchet WebSocket.
I want to broadcast a message to all connected WebSocket clients when an API endpoint is hit, for example:
// Laravel controller
public function startStockOpname(Request $request) {
Redis::publish("stock-opname.{$branchCode}", json_encode([
'message' => 'Stock Opname Started',
'role' => 'auditor',
'branch_code' => $branchCode,
]));
}
On the other side, the WebSocket server (Ratchet) is subscribing to the Redis channels and then broadcasting messages to clients.
This is the flow I aim for:
Frontend ➜ API call to Laravel ➜ Laravel publishes via Redis ➜ WebSocket server receives ➜ Broadcast to WebSocket clients
But it doesn't work.
The WebSocket server starts fine and subscribes using psubscribe('stock-opname.*')
But when I hit the Laravel API, no message is received in the WebSocket server.
The clients never receive the message.
I tried dumping the message in Laravel — it's publishing, but the WebSocket server does not respond.
However, if I send the message directly from the frontend via websocket.send()
, everything works perfectly and all clients receive the message.
this.websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Message received:', data);
// Jika auditor memulai stock opname
if (data.message === 'Stock Opname Started/Resumed' && data.role === 'auditor') {
this.isDisabledStart = false;
this.remainingSeconds = 60;
this.startCountdownTimer();
}
};
So now I'm confused:
When using Ratchet in Laravel, is Redis mandatory if I want to send messages from Laravel (API/controller) to WebSocket clients?
Is Redis not needed if the frontend directly sends messages to the WebSocket server via .send()
?
Why would Redis::publish()
in Laravel fail to trigger a broadcast in Ratchet, even though everything seems connected?
Yes, Redis or any other message broker is essential if Laravel and the WebSocket server are running in separate processes. Laravel cannot directly call methods in the Ratchet server; it must use a broker like Redis to pass messages between these isolated components.
.send()
?Correct. In this case, the frontend communicates directly with the WebSocket server. Redis is unnecessary because there is no Laravel interaction involved in message delivery.
Redis::publish()
in Laravel not trigger any response in Ratchet?The WebSocket server likely uses a blocking subscription (psubscribe
) that:
Stops the event loop from executing.
Prevents handling of WebSocket connections or Redis messages in real-time.
Lacks proper asynchronous integration with the Ratchet loop.
Additionally, Laravel and Ratchet may be connected to different Redis instances, ports, or databases, which should be verified.
The correct way to receive Redis messages in a non-blocking way is by using the react/redis
package, which is fully compatible with the Ratchet event loop (ReactPHP
). This allows the WebSocket server to simultaneously listen to Redis and handle WebSocket client connections.
Install react/redis
and Ratchet (if not already installed):
composer require clue/redis-react
composer require cboden/ratchet
Here is a simplified example of a WebSocket server using Ratchet and react/redis
to handle Redis messages and broadcast them to WebSocket clients:
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use React\EventLoop\Factory as LoopFactory;
use React\Socket\Server as SocketServer;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use Clue\React\Redis\Factory as RedisFactory;
class StockOpnameServer implements MessageComponentInterface
{
public $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $from, $msg) {}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
$conn->close();
}
public function broadcast($data)
{
foreach ($this->clients as $client) {
$client->send($data);
}
}
}
// Create ReactPHP loop
$loop = LoopFactory::create();
// Create the WebSocket server instance
$stockServer = new StockOpnameServer();
// Set up WebSocket server
$socket = new SocketServer('0.0.0.0:8080', $loop);
$server = new IoServer(
new HttpServer(
new WsServer($stockServer)
),
$socket,
$loop
);
// Subscribe to Redis
$redisFactory = new RedisFactory($loop);
$redisFactory->createSubscriber('127.0.0.1:6379')->then(function ($redis) use ($stockServer) {
$redis->psubscribe('stock-opname.*');
$redis->on('pmessage', function ($pattern, $channel, $message) use ($stockServer) {
$stockServer->broadcast($message);
});
});
// Run the loop
$loop->run();
This example:
Starts a WebSocket server.
Subscribes to all Redis channels that match stock-opname.*
.
Forwards any Redis message received to all connected WebSocket clients.
Ensure that Laravel and your WebSocket server are using the same Redis configuration (host, port, database index):
Check in Laravel: config/database.php
'redis' => [
'client' => 'phpredis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
],
Ensure that REDIS_HOST
, REDIS_PORT
, and REDIS_DB
match what the WebSocket server is using.
Use react/redis
in the WebSocket server.
Ensure Laravel and the WebSocket server use the same Redis instance.
Integrate Redis subscription into the same event loop as Ratchet.
Replace blocking psubscribe()
with async handling from react/redis
.
Confirm Redis::publish()
sends to correct channel (e.g., stock-opname.branch1
).
Confirm Redis receives messages by testing with redis-cli
.