Using laravel-echo-server with React Native
This article was originally published on beqode.com with proper code highlighting & style
Let’s build a proof-of-concept chat app using the following technologies:
Laravel: Simple chat backend
Our Proof-of-concept will simply broadcast a new chat message to all users of a public chat room.
$ composer create-project --prefer-dist laravel/laravel chat-server "5.8.*"
Eloquent models
$ php artisan make:model Chat --migration$ php artisan make:model ChatMessage --migration$ php artisan make:migration create_chat_user_table --create=chat_user<?php
// chat-server/app/Chat.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Chat extends Model
{
//
}
// chat-server/app/ChatMessage.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ChatMessage extends Model
{
protected $visible = [
'id',
'text',
];
protected $fillable = [
'chat_id',
'user_id',
'text',
];
public function chat()
{
return $this->belongsTo(Chat::class);
}
}
// chat-server/database/migrations/2019_11_27_104925_create_chats_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateChatsTable extends Migration
{
public function up()
{
Schema::create('chats', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('chats');
}
}
// chat-server/database/migrations/2019_11_27_104937_create_chat_messages_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateChatMessagesTable extends Migration
{
public function up()
{
Schema::create('chat_messages', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('text');
$table->unsignedBigInteger('chat_id');
$table->unsignedBigInteger('user_id');
$table->timestamps();
$table
->foreign('chat_id')
->references('id')
->on('chats');
$table
->foreign('user_id')
->references('id')
->on('users');
});
}
public function down()
{
Schema::dropIfExists('chat_messages');
}
}
// chat-server/database/migrations/2019_11_27_104948_create_chat_user_table.php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateChatUserTable extends Migration
{
public function up()
{
Schema::create('chat_user', function (Blueprint $table) {
$table->unsignedBigInteger('chat_id');
$table->unsignedBigInteger('user_id');
$table->primary(['chat_id', 'user_id']);
$table
->foreign('chat_id')
->references('id')
->on('chats')
->onDelete('cascade');
$table
->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('chat_user');
}
}
ChatMessageObserver
$ php artisan make:observer ChatMessageObserver --model=ChatMessage
It’s a good idea to put side effects in Model Observers. broadcast
is a good example of a side effect: Regardless of how and when a ChatMessage
has been created, we want to broadcast the associated event.
<?php
// chat-server/app/Observers/ChatMessageObserver.php
namespace App\Observers;
use App\ChatMessage;
use App\Events\ChatMessageCreated as ChatMessageCreatedEvent;
class ChatMessageObserver
{
public function created(ChatMessage $message)
{
broadcast(new ChatMessageCreatedEvent($message));
}
}
// chat-server/app/Providers/AppServiceProvider.php
public function boot()
{
ChatMessage::observe(ChatMessageObserver::class);
}
ChatMessageCreated Event
$ php artisan make:event ChatMessageCreated
Our “chat message created” event will be broadcasted on a simple open/public channel.
Note: Don’t forget to add
implements ShouldBroadcast
to your Event class!
<?php
// chat-server/app/Events/ChatMessageCreated.php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use App\ChatMessage;
class ChatMessageCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public function __construct(ChatMessage $message)
{
$this->message = $message;
}
public function broadcastOn()
{
return new Channel('chats.' . $this->message->chat->id);
}
}
Chat public channel
Since our simple example does not require channel authentication/authorization, we do not need to declare our public chat channel in routes/channels.php
file.
This is not quite a real-world example, since literally anybody can join the chats.*
channels.
Achieving channel authentication & authorization via Laravel Passport will be part of another post.
React Native: Receiving broadcast
Quickly setup a new React Native app:
$ react-native init ChatClient$ cd ChatClient/$ yarn add laravel-echo socket.io-client
Let’s configure our socket directly in App.js
file:
// ChatClient/App.js
import Echo from 'laravel-echo';
import socketio from 'socket.io-client';
const echo = new Echo({
host: 'http://127.0.0.1:6001',
broadcaster: 'socket.io',
client: socketio,
});
echo
.channel('chats.1')
.listen('ChatMessageCreated', ev => console.log(ev.message.text));
const App: () => React$Node = () => {
// ...
Note:
chats.1
will be seeded in next section.
Running the demo
Redis broadcaster
We will not use the default Pusher broadcast driver. We want to use a 100% free solution based on open source software:
- Redis as our Laravel broadcaster
- laravel-echo-server as our Socket.IO server (this listens to our Redis broadcaster)
- Echo+Socket.IO as our React Native Socket.IO client
Follow the official documentation for configuring broadcasting, as well as Redis, but in a nutshell:
- Uncomment
BroadcastServiceProvider
inconfig/app.php
$ composer require predis/predis
- Set
redis
as yourBROADCAST_DRIVER
in your local.env
file
Note: Don’t forget to actually install & start Redis on your machine.
Queue listener
As you may have read in the documentation, we need to setup a queue listener for Laravel to broadcast our application events.
All event broadcasting is done via queued jobs so that the response time of your application is not seriously affected.
In a nutshell:
- Leave the defaults in
config/database.php
redis.default
- Set
redis
as yourQUEUE_CONNECTION
in your local.env
file
laravel-echo-server
laravel-echo-server is a Socket.IO server compatible with Laravel Echo. This is where our ChatClient app will connect in order to receive updates from the Laravel backend.
$ yarn global add laravel-echo-server
As per the official documentation, let’s generate the socket server configuration file, just accept all the defaults, we will be editing the file manually later on.
$ laravel-echo-server init
Note: Adjust manually
devMode
setting totrue
since default value isfalse
.
Since we won’t be using the light http API of the socket server, we don’t need to generate a client ID & secret.
Starting from Laravel 5.8.*
, Laravel events are namespaced in Redis (certainly a good idea) via a newprefix
option that can be configured to your liking:
// config/database.php (dot notation)'redis.options.prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_')
As you can see, by default, the namespace/prefix is laravel_database_
.
laravel-echo-server default configuration does not play well with this new setting. We need to explicitly specify the keyPrefix
in the socket server config:
// laravel-echo-server.json (dot notation)"databaseConfig.redis.keyPrefix": "laravel_database_"
Seeding the database
Since creating new chats/users is out of scope, let’s just seed the database manually for the demo.
$ php artisan migrate$ php artisan tinker>>> User::create([ 'name' => 'John', 'email' => 'john@example.org', 'password' => Hash::make('john_pass') ]);>>> Chat::create();>>> DB::table('chat_user')->insert([ ['chat_id' => 1, 'user_id' => 1]]);
Note: We’ve only created one user being alone in a chat room. We’re just going to assume that there are more users listening to this chat room.
Run !
Quite a bunch of stuff to run.
$ php artisan serve$ php artisan queue:work$ laravel-echo-server start$ react-native run-ios
Now let’s create a ChatMessage
in our public chat room.
$ php artisan tinker>>> ChatMessage::create(['chat_id' => 1, 'user_id' => 1, 'text' => 'Hello World']);
If everything goes well, you should see Hello World
printed in the Developer console
of your React Native app!