DEV Community

Midhun Manu
Midhun Manu

Posted on • Edited on

How to make a Chat App using Laravel 11 , Angular 17 and Web Sockets

Hello Developers I am Midhun and in this small blog I will show you how to create a simple real time chat app using Laravel 11and Angular 17.

Let's start with out Back End

  1. Create a Laravel project
    composer create-project laravel/laravel
    server

  2. php artisan install:api

  3. Install Broadcasting
    php artisan install:broadcasting

    since we are making a server - client application do no install Reverb

  4. Install Pusher
    composer require pusher/pusher-php-server

  5. In your .env add Pusher configs

    PUSHER_APP_ID="your-pusher-app-id"
    PUSHER_APP_KEY="your-pusher-key"
    PUSHER_APP_SECRET="your-pusher-secret"
    PUSHER_HOST=
    PUSHER_PORT=443
    PUSHER_SCHEME="https"
    PUSHER_APP_CLUSTER="mt1"

    ensure that you set your Broadcast Connection as pusher instead of logs in your .env

  6. Now we will make an event
    php artisan make event:ChatMessageEvent

  7. In our ChatMessageEvent

<?php

namespace App\Events;

use App\Models\Chat\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

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

    public Message $message;

    public function __construct($message)
    {
        $this->message = $message;
    }

    public function broadcastOn(): array
    {
        return [
            new Channel('chat'),
        ];
    }

    public function broadcastAs(): string
    {
        return 'message';
    }
}
Enter fullscreen mode Exit fullscreen mode

For our web sockets we need the ShouldBroadcast interface.
The BroadCastOn method gives the channel and
the BroadCastAs method gives the event.

  1. Create a Controller

php artisan make:controller ChatController

<?php

namespace App\Http\Controllers;

use App\Events\ChatMessageSent;
use App\Events\UserJoined;
use Illuminate\Http\Request;

class ChatController extends Controller
{
    public function sendMessage(Request $request)
    {
        $username = $request->input("username");
        $message = $request->input("message");

        event(new ChatMessageSent($username, $message));
        return response()->json(["status" => "Message Sent"], 200);
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Add the route in api.php

Route::post("/send", [ChatController::class, "sendMessage"]);

  1. Start the server

php artisan serve

  1. Run the queue php artisan queue:work

Let's Start the Angular Front End Now

  1. make an Angular App

ng new client --standalone false --strict false

  1. install pusher-js

npm i pusher-js

  1. create the components
ng g c chat
ng g c login
Enter fullscreen mode Exit fullscreen mode
  1. let's connect the client to sockets

in chat.component.ts

this is what we are going to use

this.pusherInstance = new Pusher("PUSHER_KEY", {
      cluster: "ap2"
    });

    this.channel = this.pusherInstance.subscribe("chat");
    this.channel.bind("message", (data:any) => {
      console.log(JSON.stringify(data))

    })

Enter fullscreen mode Exit fullscreen mode
  1. so this is how out chat component be:
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import Pusher from 'pusher-js';
interface Message {
  username: string;
  message:string;
}

@Component({
  selector: 'app-chat',
  templateUrl: './chat.component.html',
  styleUrl: './chat.component.css'
})
export class ChatComponent implements OnInit {

  username = "username";
  message = "";
  messages: Message[] = [];
  users: string[] = [];
  pusherInstance:any;
  channel: any;
  currentUser = "";

  ngOnInit(): void {
    this.username = history.state.currentUser;
    this.users.push(this.username);
    this.pusherInstance = new Pusher("PUSHER_KEY", {
      cluster: "ap2"
    });

    this.channel = this.pusherInstance.subscribe("chat");
    this.channel.bind("message", (data:any) => {
      console.log(JSON.stringify(data))
      this.messages.push(data)
    })
  }

  constructor(private http:  HttpClient) {}

  isCurrentUser(username:string): boolean {
    return username === this.username;
  }

  send() {
    this.http.post("http://localhost:8000/api/send", {
      "username": this.username,
      "message": this.message
    }).subscribe((res)=> {
      this.message = "";
    }, (error) => {
      console.error(error);
    })
  }

}

Enter fullscreen mode Exit fullscreen mode

now let's write our html part

<div class="card mx-4">
  <div class="card-body d-flex flex-column" style="height: 500px;">
    <div class="d-flex align-items-center mb-3">
      <h5 class="card-title mb-0 me-3">{{username}}</h5>  
    </div>
    <hr>

    <ul class="list-unstyled d-flex flex-column flex-grow-1 mb-3 overflow-auto">
      <li *ngFor="let msg of messages" 
          class="d-flex mb-2" 
          [ngClass]="{'justify-content-end': msg.username === username, 'justify-content-start': msg.username !== username}">
        <div class="d-flex flex-column">
          <div [ngClass]="msg.username === username ? 'message-right' : 'message-left'">
            {{msg.message}}
          </div>
        </div>
      </li>
    </ul>
  </div>

  <div class="d-flex p-3 border-top">
    <input
      type="text"
      class="form-control me-2"
      placeholder="Start typing..."
      [(ngModel)]="message"
    />
    <button (click)="send()" class="btn btn-success">Send</button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

we will need a simple login component to get current user
login.component.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrl: './login.component.css'
})
export class LoginComponent {
  username: string = "";
  constructor(private router: Router) {}
  login() {
    console.log(this.username)
    this.router.navigate(["chat"], {state: {currentUser: this.username}});
  }
}
Enter fullscreen mode Exit fullscreen mode

login.component.html

<div class="d-flex justify-content-center align-items-center vh-100 bg-light">
    <div class="card shadow" style="width: 100%; max-width: 400px;">
        <div class="card-body">
            <h5 class="card-title text-center mb-4">Let's Chat</h5>
                <div class="mb-3">
                    <input type="text" [(ngModel)]="username" class="form-control" placeholder="Username" aria-label="Username">
                </div>
                <button (click)="login()" class="btn btn-danger w-100">Let's Go</button>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
unitydev_f0d1be3a56bbe6ec profile image
Unitydev

Thank you, I was looking for a solution to my web socket application.