How To Setup Real-Time Synchronization with Laravel 11 Pusher & Angular 18?

WebSocket Real-Time Synchronization With Pusher & laravel 11

Last Updated On - August 5th, 2024 Published On - Aug 01, 2024


How To Setup WebSocket Communication Among Different Screens with Laravel 11 Pusher & Angular 18?


In this tutorial, we’ll walk through the steps to set up real-time synchronization using Laravel 11 Pusher on an AWS EC2 instance. We’ll deploy a Laravel backend, configure Pusher for broadcasting events, and connect it to an Angular frontend. This setup ensures that any action taken on one screen is immediately reflected on other connected screens.


Example: Real-Time Task Tracker Application with Laravel 11, Pusher and Angular 18


Let us take an example of Real-Time Task Tracker Application to understand Real-Time Synchronization process. In this example, we will create a real-time task tracker application using Laravel 11 for the backend, Angular 18 for the frontend, and Pusher for WebSocket communication. The task tracker will allow users to create, update, and track tasks in real-time, synchronizing the task list across multiple browsers and devices.


Prerequisites


Before we start, make sure you have the following installed:

  • PHP
  • Composer
  • Node.js and npm
  • Angular CLI
  • An AWS EC2 instance with Ubuntu and Apache (or another server setup)
  • Pusher account



Step 1: Setup Pusher Account


Create Account

You need to create a Pusher account in order to access their socket communication functionality.

Create a Channel Application

Create a channel application with any name of your choice, server cluster, frontend, and backend tech stack.

Pusher Create Channel
Pusher Channel Configuration

Frontend and Backend Configurations

Once you create app, you will be redirected to getting started page. On this page select your frontend and backend tech stack to get sample code with configuration variables. Note down all the configuration variables(highlighted in the images of sub-sections)

Frontend

As we’re using Angular 18 with Laravel as backend, we need to install laravel-echo and pusher-js via npm.

npm install --save laravel-echo pusher-js

Backend

In laravel we’ve to install pusher-php-server package via composer and save the configuration variables in .env file(explained in further sections)

composer require pusher/pusher-php-server
Backend Pusher Configuration Laravel

Step 2: Setting Up the Backend with Laravel


Install Laravel

composer create-project --prefer-dist laravel/laravel task-tracker
cd task-tracker

Configure Environment Variables

Edit your .env file to include your database and Pusher credentials(Highlighted in Backend section of Step1: Setup Pusher Account )

BROADCAST_DRIVER=pusher

PUSHER_APP_ID=your-pusher-app-id
PUSHER_APP_KEY=your-pusher-app-key
PUSHER_APP_SECRET=your-pusher-app-secret
PUSHER_APP_CLUSTER=mt1

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=task_tracker
DB_USERNAME=root
DB_PASSWORD=

Create Task Model and Migration

php artisan make:model Task -m

Define Task Table Schema

Update the create_tasks_table migration file

public function up()
{
    Schema::create('tasks', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->boolean('completed')->default(false);
        $table->timestamps();
    });
}

Run Migrations

Create a new MySQL database called task_tracker and run the migrations to create tasks table in the database.

php artisan migrate

Create Task Controller With Resource

php artisan make:controller TaskController --resource

Define Task Routes

Add routes to routes/api.php

use App\Http\Controllers\TaskController;

Route::resource('tasks', TaskController::class);

Implement TaskController Methods

namespace App\Http\Controllers;

use App\Events\TaskUpdated;
use App\Models\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        return Task::all();
    }

    public function store(Request $request)
    {
        $task = Task::create($request->all());
        event(new TaskUpdated('create', $task));
        return $task;
    }

    public function update(Request $request, Task $task)
    {
        $task->update($request->all());
        event(new TaskUpdated('update', $task));
        return $task;
    }

    public function destroy(Task $task)
    {
        $task->delete();
        event(new TaskUpdated('delete', $task));
        return response()->json(null, 204);
    }
}

Create TaskUpdated Event

Create a TaskUpdate event to broadcast the data to all connected devices

php artisan make:event TaskUpdated
namespace App\Events;

use App\Models\Task;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TaskUpdated implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $action;
    public $task;

    public function __construct($action, Task $task)
    {
        $this->action = $action;
        $this->task = $task;
    }

    public function broadcastOn()
    {
        return new Channel('tasks');
    }
}



Step 3: Deploy Laravel on AWS EC2 – (Optional)


SSH into your EC2 instance

ssh -i /path/to/your-key.pem ubuntu@your-ec2-public-ip

Install Necessary Software

sudo apt update
sudo apt upgrade -y
sudo apt install -y apache2 libapache2-mod-php php php-cli php-mbstring php-xml php-bcmath php-curl unzip

Install Composer

cd ~
curl -sS https://getcomposer.org/installer -o composer-setup.php
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

Clone your Laravel Project

cd /var/www/html
sudo git clone https://github.com/yourusername/task-tracker.git
cd task-tracker
sudo composer install
sudo cp .env.example .env
sudo php artisan key:generate

Update .env as described in Step 2.2

Set File Permissions

sudo chown -R www-data:www-data /var/www/html/task-tracker
sudo chmod -R 775 /var/www/html/task-tracker/storage
sudo chmod -R 775 /var/www/html/task-tracker/bootstrap/cache

Configure Apache

sudo nano /etc/apache2/sites-available/task-tracker.conf

Add the following content

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/task-tracker/public
    ServerName api.xyz.com

    <Directory /var/www/html/task-tracker>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    <Directory /var/www/html/task-tracker/public>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

Enable the new site and the Apache rewrite module

sudo a2ensite task-tracker.conf
sudo a2enmod rewrite
sudo systemctl restart apache2



Step 4: Setting Up the Frontend with Angular


Create an Angular Project

ng new task-tracker-frontend
cd task-tracker-frontend

Install Laravel Echo and Pusher

npm install --save laravel-echo pusher-js

Create Echo Service

Create a new service to initialize Echo in your Angular project

import { Injectable } from '@angular/core';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

@Injectable({
  providedIn: 'root'
})
export class EchoService {
  public echo: Echo;

  constructor() {
    (window as any).Pusher = Pusher;

    this.echo = new Echo({
      broadcaster: 'pusher',
      key: 'your-pusher-app-key',
      cluster: 'mt1',
      forceTLS: true,
    });
  }
}

Create Task Service

Create a service to handle task-related actions

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { EchoService } from './echo.service';

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  constructor(private http: HttpClient, private echoService: EchoService) {}

  getTasks(): Observable<any> {
    return this.http.get('http://api.xyz.com/api/tasks');
  }

  createTask(task: any): Observable<any> {
    return this.http.post('http://api.xyz.com/api/tasks', task);
  }

  updateTask(task: any): Observable<any> {
    return this.http.put(`http://api.xyz.com/api/tasks/${task.id}`, task);
  }

  deleteTask(taskId: number): Observable<any> {
    return this.http.delete(`http://api.xyz.com/api/tasks/${taskId}`);
  }

  listenForTaskUpdates(callback: (data: any) => void) {
    this.echoService.echo.channel('tasks').listen('TaskUpdated', callback);
  }
}

Create Task Component

import { Component, OnInit } from '@angular/core';
import { TaskService } from './task.service';

@Component({
  selector: 'app-task',
  templateUrl: './task.component.html',
  styleUrls: ['./task.component.css']
})
export class TaskComponent implements OnInit {
  tasks: any[] = [];
  newTask: any = { title: '', completed: false };

  constructor(private taskService: TaskService) {}

  ngOnInit(): void {
    this.loadTasks();

    this.taskService.listenForTaskUpdates((data: any) => {
      this.handleTaskUpdate(data);
    });
  }

  loadTasks() {
    this.taskService.getTasks().subscribe(tasks => {
      this.tasks = tasks;
    });
  }

  addTask() {
    this.taskService.createTask(this.newTask).subscribe(task => {
      this.tasks.push(task);
      this.newTask = { title: '', completed: false };
    });
  }

  updateTask(task: any) {
    this.taskService.updateTask(task).subscribe();
  }

  deleteTask(taskId: number) {
    this.taskService.deleteTask(taskId).subscribe(() => {
      this.tasks = this.tasks.filter(task => task.id !== taskId);
    });
  }

  handleTaskUpdate(data: any) {
    const task = data.task;
    switch (data.action) {
      case 'create':
        this.tasks.push(task);
        break;
      case 'update':
        const index = this.tasks.findIndex(t => t.id === task.id);
        if (index !== -1) {
          this.tasks[index] = task;
        }
        break;
      case 'delete':
        this.tasks = this.tasks.filter(t => t.id !== task.id);
        break;
    }
  }
}

Add Task Component Template

<div>
  <h1>Task Tracker</h1>
  <ul>
    <li *ngFor="let task of tasks">
      <input type="checkbox" [(ngModel)]="task.completed" (change)="updateTask(task)">
      {{ task.title }}
      <button (click)="deleteTask(task.id)">Delete</button>
    </li>
  </ul>
  <input [(ngModel)]="newTask.title" placeholder="New task">
  <button (click)="addTask()">Add Task</button>
</div>

By following these steps, you will have a real-time task tracker application where tasks can be created, updated, and deleted in real-time, synchronizing the task list across multiple devices and browsers. This tutorial covers the setup and deployment of both the backend with Laravel and the frontend with Angular, using Pusher for real-time communication.