DEV Community

Cover image for Mastering Laravel Reverb & React TypeScript: A Step-by-Step Guide to Real-Time Broadcasting 🚀
waiyan woody
waiyan woody

Posted on

Mastering Laravel Reverb & React TypeScript: A Step-by-Step Guide to Real-Time Broadcasting 🚀

Real-time broadcasting can be challenging, especially when setting up communication between a backend API and a frontend application — such as Laravel API with React.

Laravel Reverb offers built-in support for monolithic applications, Inertia, and Livewire. However, when working with APIs, additional configuration steps are required. These steps can be complex for beginners, and finding solutions on platforms like YouTube or Stack Overflow isn’t always easy.

In this guide, we walked through the installation, configuration, and setup process to ensure your backend and frontend communicate effectively using private channels.

//Now let’s start

First, we need to set up broadcasting in Laravel. Since we’re using Reverb,

install it with the following command:

php artisan install:broadcasting
Enter fullscreen mode Exit fullscreen mode

When prompted to install Reverb, select Yes
âš  Important: Since we are working with an API,
we don't need to install the Node.js dependencies.
When asked, choose No to skip the installation.
and for client app (react) , we need to install laravel-echo and pusher-js

npm install laravel-echo pusher-js
Enter fullscreen mode Exit fullscreen mode

Now that you’ve installed all the required packages for both the frontend and backend, let’s proceed with the configuration.

After installing Reverb, you’ll find the following credentials in your backend .env file:

REVERB_APP_ID=000000
REVERB_APP_KEY=adfsadfadfsdfsdfsfsdf
REVERB_APP_SECRET=asdfasdfsdfsdf
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
Enter fullscreen mode Exit fullscreen mode

copy those credentials and write in your frontend .env like this

VITE_REVERB_APP_ID=000000
VITE_REVERB_APP_KEY=adfsadfadfsdfsdfsfsdf
VITE_REVERB_APP_SECRET=asdfasdfsdfsdf
VITE_REVERB_HOST="localhost"
VITE_REVERB_PORT=8080
VITE_REVERB_SCHEME=http
Enter fullscreen mode Exit fullscreen mode

Since I’m using React with Vite, my .env configuration looks like this:

If you’re using a different setup, your configuration may vary accordingly

🔒Now, let’s move to the authorization step. If this isn’t configured properly, your application will encounter a 403 Forbidden error when accessing private channels.

You need to configure bootstrap/app.php in your Laravel backend.

->withRouting(
    web: __DIR__ . '/../routes/web.php', 
    // you need to comment channel declaration here
    api: __DIR__ . '/../routes/api.php',

) 
// and add this to authorize your private channels
->withBroadcasting(
    __DIR__ . '/../routes/channels.php',
    ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
)
Enter fullscreen mode Exit fullscreen mode

Be sure to check your config/cors.php file. In the ‘allowed_origins’ setting, make sure to add the URL of your frontend application:

'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:5173')],
Enter fullscreen mode Exit fullscreen mode

On the frontend, create a file named echo.js (or echo.ts if you're using TypeScript) and configure it as follows:

This is the TypeScript example

import axios from "axios";
import Echo from "laravel-echo";
import Pusher from "pusher-js";

// Ensure TypeScript recognizes Pusher globally
declare global {
  interface Window {
    Pusher: typeof Pusher;
    Echo: Echo;
  }
}
window.Pusher = Pusher;
window.Echo = new Echo({
  broadcaster: "reverb",
  key: import.meta.env.VITE_REVERB_APP_KEY as string, // Explicitly cast environment variable
  authorizer: (channel, options) => {
    return {
      authorize: (socketId: string, callback: (error: boolean, data: any) => void) => {
        axios
          .post("<http://127.0.0.1:8000/api/broadcasting/auth>", {
            socket_id: socketId,
            channel_name: channel.name,
          },{
                headers: {
                Authorization: `Bearer ${localStorage.getItem('token')}`,
                },
            })
          .then((response) => {
            callback(false, response.data);
          })
          .catch((error) => {
            callback(true, error);
          });
      },
    };
  },
  wsHost: import.meta.env.VITE_REVERB_HOST as string,
  wsPort: (import.meta.env.VITE_REVERB_PORT as unknown as number) ?? 80,
  wssPort: (import.meta.env.VITE_REVERB_PORT as unknown as number) ?? 443,
  forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? "https") === "https",
  enabledTransports: ["ws", "wss"],
});
export default window.Echo;
Enter fullscreen mode Exit fullscreen mode

and For JavaScript you can configure like this

import axios from "@/utils/axios";
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post('/api/broadcasting/auth', {
socket_id: socketId,
channel_name: [channel.name](<http://channel.name/>)
})
.then(response => {
callback(false, response.data);
})
.catch(error => {
callback(true, error);
});
}
};
},
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});
Enter fullscreen mode Exit fullscreen mode

//That’s all you need to do for the configuration! Now, let’s start both the backend and frontend:

Start the Laravel backend

php artisan serve
Enter fullscreen mode Exit fullscreen mode

Start Laravel Reverb with debugging enabled

php artisan reverb:start --debug
Enter fullscreen mode Exit fullscreen mode

Start the queue listener

php artisan queue:listen
Enter fullscreen mode Exit fullscreen mode

Start the frontend

npm run dev
Enter fullscreen mode Exit fullscreen mode

Let’s test this with a private channel ,

First declare your private channel at routes/channels.php like this

// Private Channel (Only authenticated users can listen)
Broadcast::channel('private-chat.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
Enter fullscreen mode Exit fullscreen mode

Private Channel → Only allows a specific user to listen (e.g., messages for userId).

And Run this command to create an event:

php artisan make:event PrivateMessageEvent
Enter fullscreen mode Exit fullscreen mode

Now, modify app/Events/PrivateMessageEvent.php:

class PrivateMessageEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels; 
    public string $message;
    public int $userId;

    public function __construct(string $message, int $userId)
     {
         $this->message = $message;
         $this->userId = $userId;
     }

     public function broadcastOn(): array
     {
         return [new PrivateChannel("private-chat.{$this->userId}")];
     }

     public function broadcastAs(): string
     {
         return 'PrivateMessageEvent';
     } 
}
Enter fullscreen mode Exit fullscreen mode

Modify routes/api.php to trigger test events.

// Send a private message
Route::get('/broadcast-private/{userId}', function ($userId) {
    event(new PrivateMessageEvent("Hello User {$userId}, this is a private message! 🚀", $userId));
    return response()->json(['status' => 'Private event sent']);
});
Enter fullscreen mode Exit fullscreen mode

An example of how to use a private channel on the client side. Test in your react component like this

//import your echo.js or echo 
import echo from "@/utils/echo";

useEffect(() => {
    // Private Channel Listener
    const privateChannel = echo.private(`private-chat.${userId}`);
    privateChannel.listen(".PrivateMessageEvent", (data: { message: string }) => {
      console.log("🔒 Private event received:", data);
      setPrivateMessage(data.message);
    });

    return () => {
      privateChannel.stopListening(".PrivateMessageEvent");
    };
  }, [userId]);
Enter fullscreen mode Exit fullscreen mode

I hope this article helped you understand the setup process better! If you have any questions or run into issues, feel free to drop a comment or check out the reference video (https://youtu.be/xEV7ruVUEvs) for more insights.

Happy coding! 🚀

Top comments (0)