
In this post i will demonstrate how to use sockets in laravel framework using the new laravel package laravel echo server, redis and socket io.
Requirements
- Laravel framework 5.6 or higher
- Knowledge of laravel events
- Knowledge of laravel-echo-server
- socket io
Â
Sockets and Broadcasting
Laravel framework always simplify development of web applications using it’s unique features, today i will show one important feature of these which is how to use sockets in laravel with the help of laravel broadcasting. If you refer to laravel docs here you will see a detailed explanation of how to setup broadcasting and how to use it, but let’s first understand what broadcasting means.
Broadcasting always comes with in combination events which means emit an event from one side and catch that event from the other side, the first side here refers to a server and the second side is a client so the server emit events and the client catches and listens for events and do some logic with them. This server works via a socket connection, once this socket connection is turned on it listens for any event that happens in the enclosing scope.
For example if we take a famous example which broadcasting applied mostly is a chat application, in such application specific user joins then another user joins, the socket server notices that then it notifies both users of presence of each other by emitting a presence event. On the other hand if one user sends a message to the other then the server emits an event for sending message, then the other user listens for that message and displays it.
Concepts
Channel: A channel in laravel broadcasting is like a tv channel where you register events in that channel and then listen for it, there are different types of channels (Public, Private and Presence). A single channel can contain multiple events.
Event: The event is the core object of using sockets where it defines the item to be emitted to the clients. Events must belong to a channel, in laravel you define the event using a class that implements ShouldBroadcast or ShouldBroadcastNow interface and to fire it you use whether Event::fire(‘event_name’) or event(new EventName()).
Laravel Supports multiple broadcasting drivers which are pusher, redis and log. Pusher is simple to use and setup but unfortunately it is not free so In this tutorial i will instead use redis and socketIO.
Example overview
To illustraite the idea i will create a simple laravel application, in this example we have a real-estate website where we have two types of users. The first type called “host” and is capable of adding listings and the other type is the “guest” where he is able to view the listing and book it. Now imagine that a new requirement arises, we want to display a notification to the user when another user displays the same listing page so we want to display this notification “another user looking at this listing right now“ .
Â
Project setup
Let’s setup a new laravel 5.6 project with composer:
composer create-project laravel/laravel broadcasting 5.6 --prefer-dist
change your db settings:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=<db name> DB_USERNAME=<db user> DB_PASSWORD=<db password>
Next lets create a migration for listings table:
php artisan make:migration create_listing_table
modify database/migrations/XXXX_XX_XX_XXXXX_create_listing_table.php
.... .... .... class CreateListingTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('listing', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('description'); $table->string('image'); $table->integer('price'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('listing'); } }
Now migrate
php artisan migrate
Next let’s insert some dummy data into our table so we will use laravel seeder
Open database/seeds/DatabaseSeeder.php and modify it like this:
<?php use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // $this->call(UsersTableSeeder::class); $data = [ [ "title" => "Staten Island Urby 8 Navy Pier Ct, Staten Island, NY 10304 ", "description" => " At the heart of Staten Island’s new North Shore waterfront, Urby is a total rethink for a new generation of apartment seekers. Our brand new studio, one- and two-bedroom apartments are designed to make life easy. Each rental features built-in storage and custom closets, in-home washer/dryer and oversized windows for amazing light and views of NYC. Built for convenience, there is a cafe in the lobby to keep residents powered up, communal kitchen with chef-in-residence, and digital package pick-up. Friendly to any type of commute, Urby is located steps from the Stapleton Train Stop and has parking available to residents along with a bike room. Urby also has amenities that support a healthy lifestyle including an outdoor pool, 2-floor indoor Technogym(R) fitness center, filtered communal well, and easy access to a brand new waterfront esplanade. ", "image" => "https://ar.rdcpix.com/656902932/7c0306244b27c2c56809563467b4fcdec-f0xd-w1020_h770_q80.jpg", "price" => "1850" ], [ "title" => "The Drake 6260 99th St, Rego Park, NY 11374 ", "description" => " An incomparable blend of style and sophistication awaits you at The Drake, featuring 420 new, luxury no-fee Rego Park apartments for rent. The Drake combines exceptional taste, modern design, and classic, understated elegance. The Drake's no-fee Rego Park rentals raise the bar with distinctive interiors, deluxe amenities, and a nod to its Queens heritage. One-to four-bedroom residences are available, all with flexible layouts and outdoor space. This Queens luxury rental building reflects the spirit of the original Drake, which was once a lively neighborhood movie theater. The Drake elevates your comfort and convenience in every aspect of life, as residents will enjoy access to an impressive suite of amenities. The Drake has a doorman and a resident lounge, a state-of-the-art fitness and wellness center, bike storage, laundry facilities, a spa and grooming center for pets, and a colorful playroom. The Drake welcomes you to a thriving culturally rich neighborhood, Rego Park, in the heart of Queens. With restaurants, entertainment, and transit right at your fingertips. And for all the possibilities for work and play, the M and R trains mean Manhattan is always just a quick ride away from your no-fee Queens apartment at The Drake. ", "image" => "https://ar.rdcpix.com/105515697/1a9acbed962760087f333e2ca9335b77c-f0xd-w1020_h770_q80.jpg", "price" => "2400" ], [ "title" => "EOS 100 W 31st St, New York, NY 10001 ", "description" => " EOS offers distinctive residences, curated amenities and personalized service needed to engage New York City life to the fullest — inside and out. Premier recreation and relaxation facilities for residents to enjoy include a pool, fitness center on the lower level as well as a game room and entertaining areas on the tower’s 47th floor with sweeping views of Manhattan. EOS offers 375 smoke-free rental residences consisting of studios, one, two and two bedroom + den layouts. Each apartment contains a washer/dryer, dishwasher, hardwood floors, kitchens and bathrooms with premium finishes and fixtures. Consistent with The Durst Organization’s ongoing commitment to sustainability, EOS offers residents a vision of sustainability that both respects the natural world and promotes your wellbeing. Centered around the four core elements— Water, Air, Earth and Energy— EOS gives back to the environment and provides a home where people can thrive. Sustainable features have been woven throughout the building and each residence as an integral part of its design, construction and management.Stepping out from inside EOS, some of the most desirable neighborhoods await. Electric NoMad, with its mix of restaurants, hotels and boutiques, is at your doorstep. Stroll to Midtown, Bryant Park, the Flatiron District, Chelsea and the Meatpacking District. EOS residents are also within a five block walk to the following transit lines: 1, 2, 3, 6, B, D, F, M, N, Q, R, A, C, E, PATH, Amtrak trains and various bus routes. EOS provides residents the best that New York City has to offer, both inside and out. Come discover a neighborhood that immediately feels like home. ", "image" => "https://ar.rdcpix.com/1331050435/83614eb36fe85bbd00c328f3b2628899c-f0xd-w1020_h770_q80.jpg", "price" => "3923" ], [ "title" => "Normandie Court 225 E 95th St, New York, NY 10128 ", "description" => " Normandie Court is a four tower property that occupies the entire block between Second and Third Avenues with our driveway entrance on 95th Street. In addition to 24-hour concierge and doormen, valet and package rooms, there is a roof-top health club, expanding over 25,000 sf, with spectacular views. The health club includes a pool, exercise rooms, steam and sauna rooms, sun decks, a lounge and many other amenities. There is a separate children’s playroom on the lobby level. On-site attended parking is available with direct access to the building. The neighborhood is very popular with young professionals as well as families. The services and amenities of the neighborhood make this an extremely desirable location. Note: We no longer offer the Moderate Income Program. Prices are subject to change on a daily basis. Lease terms are subject to change based on availability. ", "image" => "https://ar.rdcpix.com/1612308897/712fa47321add3f4e47ff4a603f37310c-f0xd-w1020_h770_q80.jpg", "price" => "3605" ] ]; foreach ($data as $arr) { DB::table('listing')->insert([ 'title' => $arr['title'], 'description' => $arr['description'], 'image' => $arr['image'], 'price' => $arr['price'] ]); } } }
php artisan db:seed
Now after the table is filled with dummy data let’s generate the authentication, listing model and controller, we will create two pages one that display a listing of items and the other display details of that item.
Generating Auth, Model and Controller
Run this command to generate the auth scaffolding
php artisan make:auth
This will generate the auth routes and the auth views, now let’s create a model and controller to display the listing data.
php artisan make:model Listing php artisan make:controller ListingController
Now make the below modifications to the following files:
app/Listing.php
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Listing extends Model { protected $table = "listing"; }
app/Http/Controllers/ListingController.php
<?php namespace App\Http\Controllers; use App\Listing; class ListingController extends Controller { public function getIndex() { $listings = Listing::all(); return view('listing')->with('listings', $listings); } public function getDetails($id) { $listing = Listing::find($id); return view('details')->with('listing', $listing); } }
resources/views/layouts/app.blade.php
<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Broadcasting</title> <!-- Fonts --> <link rel="dns-prefetch" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css"> <!-- Styles --> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <nav class="navbar navbar-expand-md navbar-light navbar-laravel"> <div class="container"> <a class="navbar-brand" href="{{ url('/') }}"> {{ config('app.name', 'Laravel') }} </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Left Side Of Navbar --> <ul class="navbar-nav mr-auto"> </ul> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <!-- Authentication Links --> @guest <li class="nav-item"> <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a> </li> @else <li class="nav-item dropdown"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> {{ Auth::user()->name }} <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> @endguest </ul> </div> </div> </nav> <main class="py-4"> @yield('content') </main> </div> @yield('scripts') </body> </html>
resources/views/listing.php
@extends('layouts.app') @section('content') <div class="flex-center position-ref full-height"> <div class="content"> <h2 class="title m-b-md" style="background: #ccc"> Listings </h2> <div class="row"> @foreach($listings as $listing) <article class="col-md-10"> <h4><a href="{{ url('details/' . $listing->id) }}">{{ $listing->title }}</a></h4> <img src="{{ $listing->image }}" width="250" class="pull-left img-responsive thumb img-thumbnail" /> <p> {{ substr($listing->description, 0, 50) }} </p> </article> <hr/> @endforeach </div> </div> </div> @stop
resources/views/details.php
@extends('layouts.app') @section('content') <div class="flex-center position-ref full-height"> <div class="content"> <h1 class="title m-b-md" style="background: #ccc"> {{ $listing->title }} </h1> <span><strong>Price: </strong> <i class="label label-success">$ {{ number_format($listing->price, 1) }}</i></span> <div class="row"> <article class="col-md-12"> <img src="{{ $listing->image }}" class="img-responsive thumb img-thumbnail" /> <p> {{ $listing->description }} </p> </article> </div> </div> </div> @stop
routes/web.php
<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', 'ListingController@getIndex'); Route::get('/details/{id}', 'ListingController@getDetails'); Auth::routes();
app/Http/Controllers/Auth/LoginController.php
..... ..... ..... protected $redirectTo = '/'; ..... ..... .....
app/Http/Controllers/Auth/RegisterController.php
..... ..... ..... protected $redirectTo = '/'; ..... ..... .....
Well done let’s move to the most important part of this post which is to setup laravel echo and redis
Installing dependencies
To prepare laravel echo server and socket io for use we will need to install some required dependencies:
Install npm dependencies
npm install
npm install laravel-echo
npm install socket.io-client
Install php redis
composer require predis/predis
Install laravel echo server with npm
npm install -g laravel-echo-server
Next open the .env file and change BROADCASR_DRIVER to redis
BROADCAST_DRIVER=redis
Preparing laravel echo server
Let’s prepare laravel echo server, to do this we will a config file for laravel-echo-server in our project root directory, fortunately laravel-echo-server can generate this file automatically for us using this command:
laravel-echo-server init
It will ask you a set of questions lets keep the defaults for now and be sure to select redis as the database. After it finishes it will generate a json file in your project root called “laravel-echo-server.json” like this one:
{ "authHost": "http://localhost", "authEndpoint": "/broadcasting/auth", "clients": [], "database": "redis", "databaseConfig": { "redis": {}, "sqlite": { "databasePath": "/database/laravel-echo-server.sqlite" } }, "devMode": true, "host": null, "port": "6001", "protocol": "http", "socketio": {}, "sslCertPath": "", "sslKeyPath": "", "sslCertChainPath": "", "sslPassphrase": "", "subscribers": { "http": true, "redis": true }, "apiOriginAllow": { "allowCors": false, "allowOrigin": "", "allowMethods": "", "allowHeaders": "" } }
Notice the port number, this port is used by the laravel echo server to listen to events, it’s important that no other process in your system takes this port. But we need away to make this port configurable so let’s add it to the .env
LARAVEL_ECHO_PORT=6001
Also add this as a javascript variable in main layout
resources/views/layouts/app.blade.php
<html> ..... ..... <script> window.laravel_echo_port='{{env("LARAVEL_ECHO_PORT")}}'; </script> </head> ..... ..... </html>
Generating event
Let’s create a new event:
php artisan make:event ListingViewed
Open app/Events/ListingViewed.php and update as below:
<?php namespace App\Events; use App\Listing; use Illuminate\Broadcasting\Channel; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Queue\SerializesModels; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class ListingViewed implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; public $data = []; /** * Create a new event instance. * * @return void */ public function __construct(Listing $listing) { $this->data = [ 'listing' => $listing, 'current_user' => Auth::user()->id ]; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('listing.' . $this->data['listing']->id); } }
As you see above i have implemented the ShouldBroadcastNow interface and injected the listing model to event constructor, this data will be used when we listen for the event on the client side. Also i have specified that the event will broadcast on public channel with name “listing.{id}” on the method broadcastOn()
Firing the event
We need to fire the event when the user views a listing page and if he is authenticated so open app/Http/Controllers/ListingController.php and add this line:
..... ..... public function getDetails($id) { $listing = Listing::find($id); if(Auth::check()) { event(new ListingViewed($listing)); // fire the event } return view('details')->with('listing', $listing); } .... .....
Listening for the event
To listen for the event we will call the laravel echo javascript library we just installed above. To do add a new file in resources/assets/js to import laravel echo.
resources/assets/js/laravel-echo-setup.js
import Echo from 'laravel-echo'; window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ":" + window.laravel_echo_port });
As shown we imported laravel echo using Ecmascript6 syntax and then created a new object specifying that we use socket.io as the broadcaster and the host to listen to.
Next we will need to add this file to webpack.mix.js to be transpilled to Ecmascript5Â
Open webpack.mix.js and add this line:
let mix = require('laravel-mix'); .... .... .... mix.js('resources/assets/js/laravel-echo-setup.js', 'public/js');
You can read more about webpack here.
Run this command in terminal to execute webpack scripts:
npm run dev
This will generate a javascript file in public/js/laravel-echo-setup.js , let’s add this script to our resources/views/details.blade.php
resources/views/details.blade.php
@extends('layouts.app') @section('content') <div class="flex-center position-ref full-height"> <div class="content"> <h1 class="title m-b-md" style="background: #ccc"> {{ $listing->title }} </h1> <span><strong>Price: </strong> <i class="label label-success">$ {{ number_format($listing->price, 1) }}</i></span> <div class="row"> <article class="col-md-12"> <img src="{{ $listing->image }}" class="img-responsive thumb img-thumbnail" /> <p> {{ $listing->description }} </p> </article> </div> </div> </div> @stop @section('scripts') @if(\Auth::check()) <script src="//{{ Request::getHost() }}:{{env('LARAVEL_ECHO_PORT')}}/socket.io/socket.io.js"></script> <script src="{{ url('/js/laravel-echo-setup.js') }}" type="text/javascript"></script> <script> window.Echo.channel('listing.{{$listing->id}}') .listen('ListingViewed', function (e) { if(e.data.current_user != parseInt('{{ \Auth::user()->id }}')) { showNotification("Another user looking at this listing right now"); } }); function showNotification(msg) { if (!("Notification" in window)) { alert("This browser does not support desktop notification"); } else if (Notification.permission === "granted") { // If it's okay let's create a notification var notification = new Notification(msg); } else if (Notification.permission !== "denied") { Notification.requestPermission().then(function (permission) { // If the user accepts, let's create a notification if (permission === "granted") { var notification = new Notification(msg); } }); } } </script> @endif @stop
Here we updated our view by adding the socket.io script, note that this script is available only when the laravel-socket-server is turned on. Then called window.Echo.channel() method passing in the channel we need to listen to, this channel is same channel we registered the event above.
After that we use the listen() method, here we listened on “ListingViewed” event, this is the class name of the event we just created above.
I want to show notification to other users excluding the current user, so i add a condition like this:
if(e.data.current_user != parseInt('{{ \Auth::user()->id }}')) { showNotification("Another user looking at this listing right now"); }
Here i displayed browser notification for testing but you can use any other notification system as you like.
Testing notifications
To test the notifications create two user accounts and login from two different browsers, let’s say chrome and firefox and then in your project root directory run this command to turn on laravel echo server:
laravel-echo-server start
If it works successfully it should output
L A R A V E L E C H O S E R V E R version 1.5.2 ⚠Starting server in DEV mode... ✔ Running at localhost on port 6001 ✔ Channels are ready. ✔ Listening for http events... ✔ Listening for redis events... Server ready! Channel: listing.1 Event: App\Events\ListingViewed
Now try to access any listing in firefox with the first user account and then access the same listing on chrome with the second user account and it will show the notification like this.
best tutorial
Hi Nice tutorial.
thanks
Hi!
I really appreciatte your tutorial. I did my laravel, redis, socket.io system but I have one simple question: why is better to use Redis to publish a notification in instead of wait to the response in the frontend and then emit an event to node. I thunk that is it a stupid question but can you explain me the benefits about doing in this way?
Thanks!
Actually when handling real time events you don’t have to use redis, the only technology to use is sockets which provide the running layer that catch any events. Redis is a fast data store which you can be used to do various operations like incrementing or decrementing values. When redis used alongside sockets it provide a better real time integration for example think of a website like booking.com which notifies users of the available guests remaining for booking a certain listing.
Thank you for the reply. I read a lot of tutos about realtime applications, like a simple chat. And for example all of them use Redis to connect between Laravel and Node server. My question is about, why is not enough to send notification from frontend. for example: 1. Make an ajax request from the angular, vue, react… frontend. 2. Do something in the php, Laravel… bacenkd and send a response from de frontend. 3. Emit the notification from the frontend to node server with the backend response. I didnot find the solution to solve this doubt. Why is better… Read more »
You can do this and it will work but i think ajax is not reliable in real time although there some simple chat applications written with javascript ajax and setInterval but this produce the same result as using Redis with node as this depends on the size of traffic your site has, using Redis, laravel echo and node can handle millions of users but in case of javascript ajax this will be huge overload.