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
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
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
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
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']],
)
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')],
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;
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'],
});
//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
Start Laravel Reverb with debugging enabled
php artisan reverb:start --debug
Start the queue listener
php artisan queue:listen
Start the frontend
npm run dev
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;
});
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
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';
}
}
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']);
});
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]);
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)