Using laravel-echo-server with React Native
Dec 3, 2019Let’s build a proof-of-concept chat app using the following technologies:
- Laravel
- React Native
- Redis + laravel-echo-server + Socket.io + Laravel Echo
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
// chat-server/app/Chat.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Chat extends Model
{
//
}
// 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');
}
}
php artisan make:model ChatMessage --migration
// 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_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');
}
}
php artisan make:migration create_chat_user_table --create=chat_user
// 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.
// 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!
// 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 the 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 your
BROADCAST_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 your
QUEUE_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 new “prefix” 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' => '[email protected]', '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!
If you enjoyed this article, give it a clap on Medium.