Websocket with Laravel-reverb in the backend and React Native in the frontend
Let´s explore how to use Laravel Reverb to establish a WebSocket connection between your Laravel backend and a React Native application built with Expo. I think I covered the necessary steps to set up Laravel Reverb, configure the WebSocket connection, subscribe to channels using Laravel Echo and chat with others!
1 - Laravel (Backend)
1.1 - Create a new laravel project
Instructions in the official documentation
1.2 - Install Broadcasting Reverb
Instructions in the official documentation
1.3 - Install Sanctum for API communication
Instructions in the official documentation
1.4 - Additional configurations
Based in "cors-and-cookies" in the docs
php artisan config:publish cors
config/cors.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
resources/js/bootstrap.js
import axios from "axios";
window.axios = axios;
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
// Enable credentials and XSRF token handling
window.axios.defaults.withCredentials = true;
window.axios.defaults.withXSRFToken = true;
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allow your team to quickly build robust real-time web applications.
*/
import "./echo";
bootstrap/app.php
Based in "authorizing-private-broadcast-channels" in the docs
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
// channels: __DIR__.'/../routes/channels.php',
health: '/up',
)
->withBroadcasting(
__DIR__ . '/../routes/channels.php',
['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
1.5 - Setup a Channel
routes/channels.php
<?php
use Illuminate\Support\Facades\Broadcast;
use App\Models\User;
Broadcast::channel('chat.{receiverId}', function (User $user, int $receiverId) {
return (int) $user->id === (int) $receiverId;
});
1.6 - "chat_messages" table migration
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('chat_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->integer('from');
$table->text('message');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('chat_messages');
}
};
1.7 Database seeder
Database/seeders/DatabaseSeeder.php
<?php
namespace database/Seeders/DatabaseSeeder.php;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Support\Str;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$roles = [
'Software Engineer', 'DevOps', 'Database Analyst', 'Cyber Security Consultant', 'Project Manager',
'System Architect', 'QA Engineer', 'Product Owner', 'UI/UX Designer', 'Scrum Master'
];
shuffle($roles);
foreach ($roles as $role) {
User::create([
'name' => fake()->name(),
'role' => $role,
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => Hash::make('password'),
'remember_token' => Str::random(10),
]);
}
// User::factory(10)->create();
// User::factory()->create([
// 'name' => 'Test User',
// 'email' => '[email protected]',
// ]);
}
}
1.8 - Create an Event to broadcast
php artisan make:event MessageSent
app/Events/MessageSent.php
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public $receiver;
public $sender;
public $message;
public function __construct($receiver, $sender, $message)
{
$this->receiver = [
'id' => $receiver->id,
'name' => $receiver->name,
'role' => $receiver->role,
];
$this->sender = [
'id' => $sender->id,
'name' => $sender->name,
'role' => $sender->role,
];
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('chat.' . $this->receiver['id']);
}
}
1.9 - Create Controllers
php artisan make:Controller API/Auth/_Auth
php artisan make:Controller API/ChatMessageController
app/Http/Controllers/API/Auth/_Auth.php
<?php
namespace App\Http\Controllers\API\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class _Auth extends Controller
{
public function login(Request $request)
{
try {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json([
'status' => 401,
'type' => 'error',
'title' => 'Feedback 👋',
'message' => '❌ The provided credentials are incorrect'
]);
}
return [
'status' => 200,
'type' => 'success',
'title' => 'Feedback 👋',
'message' => '✅ Everything is ok',
'data' => [
'user' => $user,
'token' => $user->createToken('login-token')->plainTextToken
]
];
} catch (\Throwable $th) {
return response()->json([
'status' => 500,
'type' => 'error',
'title' => 'Feedback 👋',
'message' => '❌ Internal server error'
]);
}
}
}
app/Http/Controllers/API/ChatMessageController.php (dispatch the event on "store" function)
<?php
namespace App\Http\Controllers\API;
use App\Events\MessageSent;
use App\Http\Controllers\Controller;
use App\Models\ChatMessage;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ChatMessageController extends Controller
{
public function store(Request $request): Response
{
$validatedData = $request->validate([
'user_id' => 'required|exists:users,id',
'from' => 'required|exists:users,id',
'message' => 'required|string',
]);
$message = ChatMessage::create($validatedData);
$receiver = User::find($request->user_id);
$sender = User::find($request->from);
// broadcast(new MessageSent($receiver, $sender, $request->message));
MessageSent::dispatch($receiver, $sender, $message);
return response([
'status' => 200,
'type' => 'success',
'title' => 'Feedback 👋',
'message' => '✅ message sent',
'data' => [
'sender' => $sender,
'message' => $message,
'receiver' => $receiver
]
]);
}
}
1.10 - Setup API routes (login and "send-message")
routes/api.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\API\Auth\_Auth;
use App\Http\Controllers\API\ChatMessageController;
Route::post('login', [_Auth::class, 'login']);
Route::get('user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
Route::middleware(['auth:sanctum'])->group(function () {
// Route::get('get-unread-messages', [ChatMessageController::class, 'getUnreadMessages']);
// Route::post('/get-team-members', [TeamController::class, 'index']);
Route::post('send-message', [ChatMessageController::class, 'store']);
});