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>
Comments (0)
Leave a Comment
No comments yet. Be the first to share your thoughts!