I'm trying to fetch chat-related data using Eloquent in Laravel 11 but I'm struggling to structure an optimized query using Eloquent relationships or joins.
I have two MySQL tables:
Desired Output: I want to fetch the following data for a specific sender (sender_id = 1):
{
"user": {
"id": 1,
"full_name": "Sender Name",
"profile_pic": "sender profile pic url"
},
"chat_message": {
"unread": 3, // count of unread messages from sender_id = 1
"last_message": "whatever it was", // last message sent by sender_id = 1
"created_at": "last message's sent time"
}
}
Raw Queries That Work:
// Fetch user details
SELECT id, full_name, profile_pic FROM users WHERE id = 196;
// Count unread messages from sender_id = 196
SELECT COUNT(id) FROM chat_messages WHERE read = 0 AND sender_id = 196 AND receiver_id = 197;
// Get last message from sender_id = 196
SELECT message, created_at
FROM chat_messages
WHERE sender_id = 196 AND receiver_id = 197
ORDER BY id DESC
LIMIT 1;
My Question:
Is there an efficient way to achieve this using Eloquent relationships or joins instead of multiple raw queries?
How can I optimize this for performance in Laravel 11?
Additional Query About Joins: How does a JOIN actually work in-depth?
Does MySQL fetch each row from Table 1 one by one and then look for a matching row in Table 2 before moving to the next row?
Or does it fetch all results from Table 1 at once and then join them with Table 2 based on constraints? Any help or insights would be greatly appreciated! Thanks in advance. 🙌
Table with dummy data for testing.
CREATE TABLE `chat_messages` (
`id` bigint(20) UNSIGNED NOT NULL,
`sender_id` bigint(20) UNSIGNED NOT NULL,
`receiver_id` bigint(20) UNSIGNED NOT NULL,
`message` text NOT NULL,
`read` tinyint(3) UNSIGNED NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `chat_messages` (`id`, `sender_id`, `receiver_id`, `message`, `read`, `created_at`) VALUES
(1, 196, 197, 'Some message 1', 0, '2025-02-14 00:04:47'),
(2, 196, 197, 'Some message 2', 0, '2025-02-14 00:10:02'),
(3, 196, 197, 'Some message 3', 0, '2025-02-14 00:11:06'),
(4, 197, 196, 'Some message 4', 0, '2025-02-14 00:11:31'),
(5, 196, 197, 'Some message 5', 0, '2025-02-14 00:13:11'),
(6, 197, 196, 'Some message 6', 0, '2025-02-14 00:13:15'),
(7, 196, 197, 'Some message 7', 0, '2025-02-14 00:13:51'),
(8, 197, 196, 'Some message 8', 0, '2025-02-14 00:14:04'),
(9, 196, 197, 'Some message 9', 0, '2025-02-14 00:14:13'),
(10, 197, 196, 'Some message 10', 0, '2025-02-14 00:14:20'),
(11, 196, 197, 'Some message 11', 0, '2025-02-14 00:14:40'),
(12, 197, 196, 'Some message 12', 0, '2025-02-14 00:14:47'),
(13, 197, 196, 'Some message 13', 0, '2025-02-14 00:15:02'),
(14, 196, 197, 'Some message 14', 0, '2025-02-14 00:15:11'),
(15, 196, 197, 'Some message 15', 0, '2025-02-14 00:25:08'),
(16, 197, 196, 'Some message 16', 0, '2025-02-14 00:25:15'),
(17, 196, 197, 'Some message 17', 0, '2025-02-14 00:25:30'),
(18, 196, 197, 'Some message 18', 0, '2025-02-14 00:27:48'),
(19, 197, 196, 'Some message 19', 0, '2025-02-14 00:27:53'),
(20, 196, 197, 'Some message 20', 0, '2025-02-14 00:27:58'),
(21, 196, 197, 'Some message 21', 0, '2025-02-14 00:28:59'),
(22, 197, 196, 'Some message 22', 0, '2025-02-14 00:29:01'),
(23, 196, 197, 'Some message 23', 0, '2025-02-14 00:29:15'),
(24, 197, 196, 'last message from 197', 0, '2025-02-14 00:29:18'),
(25, 196, 197, 'last message from 196', 0, '2025-02-14 00:29:23');
You can do it this way...
1. Change Your User.php(User Model)
like this...
class User extends Model
{
public function chatMessages()
{
return $this->hasMany(ChatMessage::class, 'sender_id');
}
public function unreadMessages($receiverId)
{
return $this->hasMany(ChatMessage::class, 'sender_id')
->where('receiver_id', $receiverId)
->where('read', 0);
}
public function lastMessage($receiverId)
{
return $this->hasMany(ChatMessage::class, 'sender_id')
->where('receiver_id', $receiverId)
->orderByDesc('id')
->limit(1);
}
}
2. Define Relationship in ChatMessage Model(i.e. ChatMessage.php)* like this...
class ChatMessage extends Model
{
protected $fillable = ['sender_id', 'receiver_id', 'message', 'created_at', 'read'];
public function sender()
{
return $this->belongsTo(User::class, 'sender_id');
}
}
Then base on sender_id
and receiver_id
you can fetch data like this..
$user = User::with([
'chatMessages' => function ($query) use ($receiverId) {
$query->where('receiver_id', $receiverId);
}
])
->where('id', $senderId)
->first();
// Count unread messages
$unreadCount = $user->unreadMessages($receiverId)->count();
// Get last message
$lastMessage = $user->lastMessage($receiverId)->first();
How does a JOIN actually work in-depth?
MySQL does not fetch all rows from Table 1 at once. Instead, it iterates over each row of Table-1
and finds matching rows in Table-2
.If there's no index, MySQL
scans the entire Table-2
for each row from Table-1
.Then moves on to the next row in table-1
.