Creating An App Using Laravel WebSockets and NextJs (PART 1)

Pius Adams Ijachi
9 min readApr 4, 2022
I googled laravel WebSockets

So recently I had to create a web socket using laravelWebsocket and well it didn’t click really quickly for me, mostly cause all the examples I saw were all chat apps with a vue frontend so I figured “Hey make it easier for someone else” so I listened to myself. So without further adieu (i googled how to spell adieu)

App Story

So we are going to create an app that lets a user request a driver, the driver gets a notification and can accept or decline the request, all this happens in real-time. So without further adieu ( i feel like I'm using this wrong)

Laravel App

First, we create a new laravel project

composer create-project laravel/laravel example-app

So let’s get straight into creating a database first we create two models

php artisan make:model Driver -mf // create a driver model, migration and factoryphp artisan make:model Request -mf // create a driver model, migration and factory

In the driver migration file we create our columns

Schema::create('drivers', function (Blueprint $table) {$table->id();$table->string('name');$table->string('email')->unique();$table->timestamp('email_verified_at')->nullable();$table->string('password');$table->softDeletes();$table->timestamps();});

In the Request migration file we create our columns and make relationships to the driver and user table (user tables are created by default in laravel)

public function up(){Schema::create('requests', function (Blueprint $table) {$table->id();$table->boolean('status')->default(false);$table->foreignId('driver_id')->constrained(); // foreign key to drivers table$table->foreignId('user_id')->constrained(); // foreign key to users table$table->softDeletes();$table->timestamps();}

Before we actually migrate , we have to create a database , i used mysql here

// in your env file
DB_CONNECTION=mysql
DB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=websocketsDB_USERNAME=rootDB_PASSWORD=// now in your cmd or bash or whatever mac uses
php artisan migrate // migrate the tables to the db

Now we can use factories and seeders to create dummy drivers and users

// in user factory  
public function definition()
{return ['name' => $this->faker->name(),'email' => $this->faker->unique()->safeEmail(),'email_verified_at' => now(),'password' => 'password', // a mutator will hash this in the user model'remember_token' => Str::random(10),];}// in the driver factory
public function definition(){return ['name' => $this->faker->name(),'email' => $this->faker->unique()->safeEmail(),'email_verified_at' => now(),'password' => 'password', // a mutator will hash this in the driver model];}

So if any of this feels foreign to you here are the official docs talking about factories, Before we can actually create the users we have to add all the columns to our models so our models now look like

<?phpnamespace App\Models;use Illuminate\Contracts\Auth\MustVerifyEmail;use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Laravel\Sanctum\HasApiTokens;
use Illuminate\Database\Eloquent\Casts\Attribute; // for casting
class User extends Authenticatable{use HasApiTokens, HasFactory, Notifiable;/*** The attributes that are mass assignable.** @var array<int, string>*/protected $fillable = ['name','email','password',];/*** The attributes that should be hidden for serialization.** @var array<int, string>*/protected $hidden = ['password','remember_token',];/*** The attributes that should be cast.** @var array<string, string>*/protected $casts = ['email_verified_at' => 'datetime',];// mutator to hash the password before saving it to the database, refer to laravel docs for more infoprotected function password(): Attribute{return Attribute::make(set: fn ($value) => bcrypt($value),);}}

same with the request and driver models

<?phpnamespace App\Models;use Illuminate\Contracts\Auth\MustVerifyEmail;use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable;use Laravel\Sanctum\HasApiTokens;use Illuminate\Database\Eloquent\Casts\Attribute; // for castingclass Driver extends Authenticatable{use HasApiTokens, HasFactory, Notifiable;protected $fillable = ['name','email','password','remember_token'];/*** The attributes that should be hidden for serialization.** @var array<int, string>*/protected $hidden = ['password','remember_token',];/*** The attributes that should be cast.** @var array<string, string>*/protected $casts = ['email_verified_at' => 'datetime',];// mutator to hash the password before saving it to the database, refer to laravel docs for more infoprotected function password(): Attribute{return Attribute::make(set: fn ($value) => bcrypt($value),);}}<?phpnamespace App\Models;use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Casts\Attribute; // for castingclass Request extends Model{use HasFactory;protected $fillable = ['status','driver_id','user_id',];protected function status(): Attribute{return Attribute::make(set: fn ($value) => $value,get: fn ($value) => [true => 'Accepted',false => 'Rejected',][$value],);}// belongs topublic function user(){return $this->belongsTo(User::class);}// belongs topublic function driver(){return $this->belongsTo(Driver::class);}}

So in our seeder run method

public function run(){\App\Models\User::factory(10)->create(); // create 10 new  users\App\Models\Driver::factory(20)->create(); // create 20 new drivers}// in our cmd or bash or i want to say apple prompt (for mac)
php artisan db:seed // populates database

We just created users and drivers now we can create a login for the drivers and user

public function login(UserLoginRequest $request){try {$credentials = $request->safe()->only(['email','password']);$user = User::where('email',$credentials['email'])->first();$token = $user->createToken('userToken')->plainTextToken;return  (new UserResource($user))->additional(['token' => $token,'message' => 'logged in successfully',]);} catch (\Throwable $e) {return response()->json(['message'=>$e->getMessage() ],400);}}

The code above gets the user if one exisits and creates and using an api resource i return a token and message with the response

// an example reponse
{
"data": { "id": 1, "email": "fmurphy@example.net", "name": "Mario Bernhard DVM" }, "token": "5|jUgKz16yzJGht9UjBVl5PkkpPPOoa6zanzq2C7jW", "message": "logged in successfully"}

So i am skipping some steps just cause it’s outside the scope of this tutorial (also lazy)

Installing Laravel Websocket

Laravel WebSockets can be installed via composer:

composer require beyondcode/laravel-websockets

The package will automatically register a service provider.

This package comes with a migration to store statistic information while running your WebSocket server. You can publish the migration file using:

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"

Run the migrations with:

php artisan migrate

Next, you need to publish the WebSocket configuration file:

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

This is the default content of the config file that will be published as config/websockets.php:

return [    /*
* This package comes with multi tenancy out of the box. Here you can
* configure the different apps that can use the webSockets server.
*
* Optionally you can disable client events so clients cannot send
* messages to each other via the webSockets.
*/
'apps' => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'enable_client_messages' => false,
'enable_statistics' => true,
],
],
/*
* This class is responsible for finding the apps. The default provider
* will use the apps defined in this config file.
*
* You can create a custom provider by implementing the
* `AppProvider` interface.
*/
'app_provider' => BeyondCode\LaravelWebSockets\Apps\ConfigAppProvider::class,
/*
* This array contains the hosts of which you want to allow incoming requests.
* Leave this empty if you want to accept requests from all hosts.
*/
'allowed_origins' => [
//
],
/*
* The maximum request size in kilobytes that is allowed for an incoming WebSocket request.
*/
'max_request_size_in_kb' => 250,
/*
* This path will be used to register the necessary routes for the package.
*/
'path' => 'laravel-websockets',
'statistics' => [
/*
* This model will be used to store the statistics of the WebSocketsServer.
* The only requirement is that the model should extend
* `WebSocketsStatisticsEntry` provided by this package.
*/
'model' => \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry::class,
/*
* Here you can specify the interval in seconds at which statistics should be logged.
*/
'interval_in_seconds' => 60,
/*
* When the clean-command is executed, all recorded statistics older than
* the number of days specified here will be deleted.
*/
'delete_statistics_older_than_days' => 60,

/*
* Use an DNS resolver to make the requests to the statistics logger
* default is to resolve everything to 127.0.0.1.
*/
'perform_dns_lookup' => false,
],
/*
* Define the optional SSL context for your WebSocket connections.
* You can see all available options at: http://php.net/manual/en/context.ssl.php
*/
'ssl' => [
/*
* Path to local certificate file on filesystem. It must be a PEM encoded file which
* contains your certificate and private key. It can optionally contain the
* certificate chain of issuers. The private key also may be contained
* in a separate file specified by local_pk.
*/
'local_cert' => null,
/*
* Path to local private key file on filesystem in case of separate files for
* certificate (local_cert) and private key.
*/
'local_pk' => null,
/*
* Passphrase for your local_cert file.
*/
'passphrase' => null
],
];

Now we have to install pusher

composer require pusher/pusher-php-server

Next, you should make sure to use Pusher as your broadcasting driver. This can be achieved by setting the BROADCAST_DRIVER environment variable in your .env file:

BROADCAST_DRIVER=pusher

Pusher Configuration

When broadcasting events from your Laravel application to your WebSocket server, the default behavior is to send the event information to the official Pusher server. But since the Laravel WebSockets package comes with its own Pusher API implementation, we need to tell Laravel to send the events to our own server.

To do this, you should add the host and port configuration key to your config/broadcasting.php and add it to the pusher section. The default port of the Laravel WebSocket server is 6001.

'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http'
],
]

You may add additional apps to your config/websockets.php file.

'apps' => [
[
'id' => env('PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'enable_client_messages' => false,
'enable_statistics' => true,
],
]

all this was gotten from laravel WebSockets docs.

Broadcasting

Broadcasting is a way for laravel to send or broadcast events over certain channels

The core concepts behind broadcasting are simple: clients connect to named channels on the frontend, while your Laravel application broadcasts events to these channels on the backend. These events can contain any additional data you wish to make available to the frontend.

php artisan make:event DriverRequested // create a driver requested eventphp artisan make:event UserRequestStatusUpdate

Before broadcasting any events, you will first need to register the App\Providers\BroadcastServiceProvider. In new Laravel applications, you only need to uncomment this provider in the providers array of your config/app.php configuration file. This BroadcastServiceProvider contains the code necessary to register the broadcast authorization routes and callbacks.

Basically, uncomment the App\Providers\BroadcastServiceProvider from the config/app.php, it is usually uncommented in a new laravel app

After that in the DriverRequested and UserRequestStatusUpdate event, we need to implement

class DriverRequested implements ShouldBroadcastclass DriverRequested implements ShouldBroadcast{use Dispatchable, InteractsWithSockets, SerializesModels;public $request;/*** Create a new event instance.** @return void*/
// i get the request passed in the constructor
public function __construct(RequestModel $request){$this->request = $request;}/*** Get the channels the event should broadcast on.** @return \Illuminate\Broadcasting\Channel|array*/public function broadcastOn(){
// this event will only the broadcasted on a private channel
// called driver.(driver id)
// we do this so each driver that logs in can listen to only their // own channel
return new PrivateChannel('driver.'. $this->request->driver_id);}public function broadcastWith(){
// this is what we are sending
return ['message' => 'You have a Request','request' => $this->request];}

Before we can send a request we need to configure a channel in our channel.php file

// this channel means only users from the users table can listen to // it
Broadcast::channel(‘User.{id}’, function ($user, $id) {
return (int) $user->id === (int) $id;},[‘guards’ => [‘users’]]);// the guard here makes sure it //authenticates from users table// this channel means only drivers from the drivers table can listen to
// it
Broadcast::channel(‘driver.{driverId}’, function ($user, $driverId) {return $user->id === Driver::findOrNew($driverId)->id;},[‘guards’ => [‘drivers’]]);

and since we want our channels to be authenticated over sanctum we have to head over to our BroadcastServiceProvider and copy the

// copy into the api.routes and specify its auth to be sanctum
Broadcast::routes(['middleware' => ['auth:sanctum']]);

so now in a user controller it can be used like

public function createRequest( Driver $driver){// $request_data = $request->validated();$request_data['user_id'] = Auth::id();$request_data['driver_id'] = $driver->id;$request = RequestModel::firstOrCreate($request_data);// create a new requestDriverRequested::dispatch($request); // now dispatch a new event to the driver return response()->json([
'message' => "Request Sent to Driver",
]);
}

and on the driver's end

class UserRequestStatusUpdate implements ShouldBroadcast{use Dispatchable, InteractsWithSockets, SerializesModels;public $request;/*** Create a new event instance.** @return void*/public function __construct(RequestModel $request){$this->request = $request;}/*** Get the channels the event should broadcast on.** @return \Illuminate\Broadcasting\Channel|array*/public function broadcastOn(){
// send a private request
return new PrivateChannel('User.'.$this->request->user_id);}public function broadcastWith(){return [ 'message' => 'Request ' .$this->request->status , 'request' => $this->request];}}

Now on the Driver controller

public function requestAction(Request $request, RequestModel $model){$request->validate(['status' => 'required|boolean',]);$model->update(['status' => $request->status]);// update the status of the requestUserRequestStatusUpdate::dispatch($model);// send a update user attached to the requestreturn response()->json(['message' => 'Request Status updated']);}

End of the laravel app

This was a long build but I hoped it cleared some stuff up in the next one we will hook it up to a next js app

part2

https://ijachipius8.medium.com/creating-an-app-using-laravel-websockets-and-nextjs-part-2-b328121a6ac4

--

--