Using laravel-echo-server with React Native

Eightyfive
Beqode
Published in
5 min readDec 3, 2019

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 in config/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 manuallydevMode setting to true since default value is false.

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!

--

--