AI Skill Report Card
Building Realtime Chat Systems
Quick Start15 / 15
PHP// Database migration for conversation system Schema::create('conversations', function (Blueprint $table) { $table->id(); $table->enum('type', ['direct', 'group']); $table->string('title')->nullable(); $table->timestamps(); $table->index(['created_at']); }); Schema::create('conversation_user', function (Blueprint $table) { $table->id(); $table->foreignId('conversation_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->enum('role', ['admin', 'member'])->default('member'); $table->timestamp('last_read_at')->nullable(); $table->timestamps(); $table->index(['conversation_id', 'user_id']); }); Schema::create('messages', function (Blueprint $table) { $table->id(); $table->foreignId('conversation_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->text('content'); $table->json('metadata')->nullable(); // attachments, reply_to $table->timestamps(); $table->index(['conversation_id', 'created_at']); });
Recommendation▾
Add concrete input/output examples for WebSocket events and API responses to strengthen the examples section
Workflow13 / 15
Progress:
- Design database schema with proper indexing
- Set up WebSocket broadcasting with Laravel Reverb
- Implement cursor pagination for message loading
- Create optimistic UI updates on frontend
- Add typing indicators and presence channels
- Implement message delivery/read tracking
- Set up background queues for media processing
- Configure push notifications with FCM
- Add Redis caching layer
- Secure with proper authentication
1. Database Architecture
PHP// Message tracking pivot table Schema::create('message_reads', function (Blueprint $table) { $table->id(); $table->foreignId('message_id')->constrained()->onDelete('cascade'); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->timestamp('delivered_at')->nullable(); $table->timestamp('read_at')->nullable(); $table->unique(['message_id', 'user_id']); });
2. Real-time Broadcasting
PHP// channels.php Broadcast::channel('conversation.{conversationId}', function ($user, $conversationId) { return $user->conversations()->where('conversation_id', $conversationId)->exists() ? ['id' => $user->id, 'name' => $user->name] : false; }); // Message sent event class MessageSent implements ShouldBroadcast { public function broadcastOn() { return new PresenceChannel('conversation.' . $this->message->conversation_id); } public function broadcastWith() { return [ 'message' => MessageResource::make($this->message), 'user' => UserResource::make($this->message->user) ]; } }
3. Cursor Pagination
PHP// MessageController public function index(Request $request, Conversation $conversation) { $messages = $conversation->messages() ->with(['user', 'reads']) ->latest() ->cursorPaginate(50); return MessageResource::collection($messages); }
4. Frontend State Management (Vue + Pinia)
JavaScript// stores/chat.js export const useChatStore = defineStore('chat', { state: () => ({ conversations: new Map(), currentConversation: null, typingUsers: new Set(), onlineUsers: new Set() }), actions: { sendMessage(content) { const tempId = `temp_${Date.now()}`; const optimisticMessage = { id: tempId, content, user: this.currentUser, status: 'sending', created_at: new Date().toISOString() }; this.addMessage(this.currentConversation.id, optimisticMessage); return this.api.sendMessage(this.currentConversation.id, content) .then(message => { this.replaceMessage(tempId, message); }) .catch(() => { this.removeMessage(tempId); }); } } });
5. Typing Indicators
PHP// TypingEvent class UserTyping implements ShouldBroadcast { public function broadcastOn() { return new PresenceChannel('conversation.' . $this->conversationId); } public function broadcastAs() { return 'typing'; } } // Frontend let typingTimer; function handleTyping() { Echo.whisper('conversation.1', 'typing', { user_id: userId }); clearTimeout(typingTimer); typingTimer = setTimeout(() => { Echo.whisper('conversation.1', 'stopped-typing', { user_id: userId }); }, 3000); }
Recommendation▾
Include Redis configuration code and caching strategies in the workflow to make implementation more complete
Examples15 / 20
Example 1: Message with Read Tracking Input: User sends "Hello world" in conversation Output:
JSON{ "id": 123, "content": "Hello world", "user": { "id": 1, "name": "John" }, "reads": [ { "user_id": 2, "delivered_at": "2024-01-01T10:00:00Z", "read_at": null } ], "created_at": "2024-01-01T09:59:45Z" }
Example 2: Queue Job for Media Processing Input: User uploads image attachment
PHPProcessImageAttachment::dispatch($message, $uploadedFile) ->onQueue('media-processing');
Recommendation▾
Streamline the workflow section by removing some redundant explanations and focusing on the most critical implementation steps
Best Practices
- Use cursor pagination to prevent message duplication during real-time updates
- Implement optimistic UI updates for immediate user feedback
- Cache online user status in Redis with TTL
- Use presence channels for typing indicators and online status
- Process media uploads in background queues
- Encrypt sensitive message content at database level
- Rate limit message sending (e.g., 10 messages per minute)
- Use virtual scrolling for conversations with thousands of messages
- Implement proper WebSocket reconnection logic
- Store last read timestamps to calculate unread counts
Common Pitfalls
- Don't use regular pagination for real-time message loading
- Don't store typing status in database - use Redis or memory
- Don't broadcast to all users - use private/presence channels
- Don't process large media files synchronously
- Don't forget to clean up old WebSocket connections
- Don't send full user objects in every message broadcast
- Don't update read status on every message view
- Don't use polling for real-time updates
- Don't forget CSRF protection on WebSocket authentication
- Don't store ephemeral data (typing, online status) permanently