Full Stack 2 min read 887 views

How to Build a Chat Application with Laravel and Vue.js

Create a real-time chat application using Laravel, Vue.js, and WebSockets with message persistence.

E
Chat application

Backend: Laravel Setup

Message Model & Migration

Schema::create('messages', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->foreignId('conversation_id')->constrained();
    $table->text('body');
    $table->timestamps();
});

Message Event

class MessageSent implements ShouldBroadcast
{
    public function __construct(
        public Message $message
    ) {}

    public function broadcastOn(): Channel
    {
        return new PrivateChannel(
            "conversation.{$this->message->conversation_id}"
        );
    }

    public function broadcastWith(): array
    {
        return [
            'message' => [
                'id' => $this->message->id,
                'body' => $this->message->body,
                'user' => $this->message->user->only(['id', 'name']),
                'created_at' => $this->message->created_at,
            ],
        ];
    }
}

Send Message Controller

public function store(Request $request, Conversation $conversation)
{
    $message = $conversation->messages()->create([
        'user_id' => auth()->id(),
        'body' => $request->body,
    ]);

    broadcast(new MessageSent($message))->toOthers();

    return response()->json($message->load('user'));
}

Frontend: Vue.js Component

<template>
    <div class="chat-container">
        <div class="messages" ref="messagesContainer">
            <div
                v-for="message in messages"
                :key="message.id"
                :class="['message', { 'own': message.user.id === userId }]"
            >
                <strong>{{ message.user.name }}</strong>
                <p>{{ message.body }}</p>
            </div>
        </div>

        <form @submit.prevent="sendMessage">
            <input v-model="newMessage" placeholder="Type a message..." />
            <button type="submit">Send</button>
        </form>
    </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue';
import Echo from 'laravel-echo';

const props = defineProps(['conversationId', 'userId']);
const messages = ref([]);
const newMessage = ref('');
const messagesContainer = ref(null);

onMounted(async () => {
    // Load existing messages
    const response = await fetch(`/api/conversations/${props.conversationId}/messages`);
    messages.value = await response.json();

    // Listen for new messages
    window.Echo.private(`conversation.${props.conversationId}`)
        .listen('MessageSent', (e) => {
            messages.value.push(e.message);
            scrollToBottom();
        });
});

const sendMessage = async () => {
    if (!newMessage.value.trim()) return;

    const response = await fetch(`/api/conversations/${props.conversationId}/messages`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ body: newMessage.value }),
    });

    const message = await response.json();
    messages.value.push(message);
    newMessage.value = '';
    scrollToBottom();
};

const scrollToBottom = () => {
    nextTick(() => {
        messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
    });
};
</script>
Share this article:
ES

Written by Edrees Salih

Full-stack software engineer with 9 years of experience. Passionate about building scalable solutions and sharing knowledge with the developer community.

View Profile

Comments (0)

Leave a Comment

Your email will not be published.

No comments yet. Be the first to share your thoughts!