I Built a Social Media Web App with Real-Time Chat System in 3 Hours 28 Minutes
Rabbi Mehedi 🇧🇩

Rabbi Mehedi 🇧🇩

Software Engineer | Designer | Artist •

I Built a Social Media Web App with Real-Time Chat System in 3 Hours 28 Minutes

9 MIN+ min read
Web Design And Development

At 1:00 PM today, I set out to construct a full-fledged, real-time chat system—complete with typing indicators, message persistence, and a responsive interface. By 4:28 PM, with occasional pauses for thought and the indispensable assistance of Cursor, I had a production-ready application that rivals the immediacy and polish of modern tools like Slack or WhatsApp. 


Laying the Foundation: Components and Architecture

My first step was to establish the core architecture for a dynamic chat experience. I built a Livewire component—named ConversationMessages—to serve as the bridge between the server and the browser. Within this component, I defined methods for:

  1. Retrieving the Latest Messages
  2. Every conversation loads its most recent fifty messages on initialization. This ensures that even if a user refreshes the page, the chat history remains intact.
  3. Sending New Messages
  4. When a user submits a message, it is immediately validated, saved to the database, and posted to all other participants via WebSocket. I used Laravel’s Eloquent models and a standard messages table schema—fields for sender ID, conversation ID, body text, and timestamps—to ensure reliable persistence.

At the same time, I configured Laravel Echo alongside Pusher to handle the WebSocket layer. In config/broadcasting.php, I specified pusher as the broadcast driver, pointed Echo at my local WebSocket server (running on ws://localhost:6001), and secured each chat room with a channel authorization callback in routes/channels.php:

php

Copy code
Broadcast::channel('conversation.{id}', function ($user, $id) {
    // Only allow users who belong to this conversation
    return $user->conversations()->where('conversation_id', $id)->exists();
});

This ensured that only authorized participants could listen for incoming messages on any private channel. Cursor’s guidance was invaluable here: it reminded me to configure AuthorizeCheck headers in my JavaScript Echo setup, so that every WebSocket handshake carried the proper authentication token.

Real-Time Nuances: Typing Indicators and Live Feedback

The inclusion of typing indicators is more than a nicety—it is a signal of modern expectations for immediacy and human connection. To make this work, I introduced a new event class, UserTyping, which implements Laravel’s ShouldBroadcast interface:

php

Copy code
namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class UserTyping implements ShouldBroadcast
{
    use InteractsWithSockets;

    public $userId;
    public $conversationId;

    public function __construct(int $userId, int $conversationId)
    {
        $this->userId = $userId;
        $this->conversationId = $conversationId;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('conversation.' . $this->conversationId);
    }

    public function broadcastWith()
    {
        return ['user_id' => $this->userId];
    }
}

Whenever a participant presses a key in the message input, the Livewire component issues:

php

Copy code
$this->emit('user.typing', $this->conversationId);

In JavaScript, I listened for that emitted event and immediately broadcasted UserTyping to the private channel:

js

Copy code
window.Echo.private(`conversation.${conversationId}`)
    .listen('UserTyping', (payload) => {
        showTypingIndicator(payload.user_id);
    });

On the frontend, a simple Blade partial—coupled with Alpine.js—renders three animated dots whenever showTypingIndicator is triggered. Cursor recommended a 500 ms debounce on Livewire’s wire:keydown hook to prevent flooding Pusher with redundant “typing” signals. Likewise, a 3-second timer clears the indicator if no further keystrokes arrive. This delicate choreography avoids stale “typing” messages and keeps the experience feeling natural.

Polishing the User Experience

Building a robust system is only half the battle; creating an interface that feels intuitive and swift is equally crucial. Over the next hour and a half, I tackled a series of user experience refinements:

  • Automatic Input Clearing
  • After sending, the message textarea resets to blank. I learned that simply emitting the sendMessage action was not enough: I had to explicitly set the Livewire-bound $messageBody property to an empty string. Cursor’s prompt on this minor yet essential detail saved me from a lingering input field that frustrated test users.
  • Smart Scrolling
  • Each time a new message arrives—whether sent by myself or broadcast by another participant—the chat container automatically scrolls to the bottom. In code, I implemented:
js

Copy code
Livewire.on('messageSent', () => {
  const lastMessage = document.getElementById(`message-${latestId}`);
  lastMessage.scrollIntoView({ behavior: 'smooth' });
});
  • Early prototypes occasionally failed to scroll when the container height already exceeded the viewport. By comparing the scroll height before and after insertion, I ensured scrollIntoView always executed at the right moment.
  • Auto-Resizing Textarea
  • A fixed-height input can feel cramped. I added a small JavaScript routine:
js

Copy code
textarea.addEventListener('input', (event) => {
  event.target.style.height = 'auto';
  event.target.style.height = `${event.target.scrollHeight}px`;
});
  • This snippet—suggested by Cursor—allows the textarea to grow with content, then shrink back if text is deleted. The result: a more comfortable typing area that never feels too large or too small.
  • Tailwind CSS Styling
  • I opted for a minimalist palette—soft grays for incoming message bubbles, crisp whites for backgrounds, and a bright accent color for the send button. Using Tailwind’s utility classes, I established consistent padding, margin, and responsive breakpoints so that the chat interface adapts seamlessly to mobile and desktop screens. Cursor’s recommendations ensured my markup remained uncluttered: I relied on flex, space-y-2, and max-w-prose to keep the layout both modern and accessible.


Overcoming Technical Hurdles

Three areas proved especially challenging, and each demanded careful problem-solving:

  1. Ensuring Reliable WebSocket Authentication
  2. Initially, I encountered “subscription failed” errors in the console. The culprit turned out to be mismatched credentials between .env, config/broadcasting.php, and my JavaScript Echo initialization. Cursor guided me to confirm that MIX_PUSHER_APP_KEY and MIX_PUSHER_APP_CLUSTER were properly compiled into process.env by Laravel Mix. Once aligned, all private-channel subscriptions succeeded on the first try.
  3. Synchronizing Typing Indicators Across Browsers
  4. Chrome and Safari sometimes handle WebSocket frames at slightly different speeds. In one test, a Safari client did not clear its typing indicator when a user stepped away. The remedy was to broadcast a “stop typing” signal after three seconds of inactivity—a fix that required me to add a Livewire hook on keyup events to reset a JavaScript timer. Cursor’s foresight in suggesting a robust debounce-and-timeout approach proved decisive.
  5. Maintaining Scroll Position with Rapid Updates
  6. When multiple messages arrived in quick succession—especially if a user scrolled up to read previous history—the chat container intermittently snapped to the bottom, disrupting the reading flow. I addressed this by checking if the user’s scroll position was already within, say, 100 pixels of the bottom; only then would I auto-scroll. Otherwise, I displayed a small “new messages” indicator button that, when clicked, smoothly scrolled the view down. This compromise balanced live updates with user control.


Reflections: What This Means for Development

Completing a feature set of this complexity—real-time messaging, typing indicators, responsive UI, authentication, and error handling—in just 3 hours and 28 minutes illustrates a profound shift in how web applications are built today. From my vantage point, several key takeaways emerge:

  1. Rapid Prototyping with AI Assistance Is Game-Changing
  2. The collaboration with Cursor allowed me to focus on the highest-level decisions—architecture, data flow, user experience—without getting bogged down in boilerplate. Cursor’s prompts for code snippets, configuration notes, and debugging advice trimmed what might otherwise have been a week’s work into a few focused hours.
  3. Real-Time Functionality Is Becoming Table Stakes
  4. A decade ago, building WebSocket-based features required considerable investment: stand-alone Node.js servers, complex scaling strategies, and custom protocols. Today, mature packages like Laravel Echo, Pusher, and Laravel-WebSockets allow even a solo developer to deliver enterprise-grade real-time experiences. In essence, the barrier to entry has dropped dramatically. Users expect instant feedback—typing indicators, push notifications, live collaboration—and the ecosystem now makes these expectations achievable within a sprint.
  5. Frontend and Backend Convergence
  6. Once, frontend interactivity and backend data logic were siloed disciplines. Livewire—and similar frameworks—blur that boundary, enabling me to write PHP methods that seamlessly update the DOM. This integration reduces context-switching, eliminates many JavaScript state-management headaches, and shortens the development feedback loop. In an industry where shipping features quickly can make or break a product’s adoption, these converged tools redefine what “minimum viable product” really means.
  7. User Experience Is Paramount, Even at Speed
  8. It would have been easy to stop after “messages send and receive.” Yet the extra 90 minutes I spent on polished UI subtleties—auto-resizing inputs, smooth scrolling, finite typing timeouts—proved invaluable when I demoed the chat to colleagues. The contrast between a bare-bones prototype and a refined experience is stark. Users forgive missing features; they do not forgive awkward or jarring interactions. This principle guides my work: no matter how quickly I move, UX should never be an afterthought.
  9. Future-Proofing and Scalability
  10. Although this system currently serves a single conversation at a time, the architecture I chose—private channels keyed by conversation ID, event-based broadcasting, and a modular Livewire component—lays the groundwork for scale. In the next phase, horizontally scaling the WebSocket server (using Amazon ElastiCache for Redis, for example) or implementing federation protocols (for decentralized chat) would require minimal rework. Planning for future needs doesn’t necessarily slow you down; in fact, making thoughtful choices early can liberate you to iterate confidently.


Looking Ahead

By 4:28 PM, I had created not just code, but a vision for streamlined development. The ability to craft a production-quality, real-time chat system—in less time than many tutorials take to walk through similar setups—underscores a broader trend: developers can now wield powerful frameworks, mature libraries, and AI-driven assistants to transform concepts into working products almost instantaneously.

As I reflect on today’s work, two thoughts resonate most strongly:

  • The New Norm of “Instant Gratification” in Development
  • When building complex features is no longer a week-long ordeal, we must recalibrate our expectations. Stakeholders will naturally demand faster delivery. Our role shifts from “how do we build this?” to “how do we build this sustainably, securely, and with elegance?”
  • The Imperative of Lifelong Learning
  • In an ecosystem that evolves monthly, the developer’s edge lies not in memorizing APIs, but in cultivating the ability to assimilate new tools—like Cursor—quickly. Today’s solution may be “Laravel Livewire plus Pusher.” Tomorrow’s solution might involve cutting-edge decentralized protocols or novel machine-learning–driven user interfaces. Embracing a mindset of perpetual curiosity and adaptability is as important as any single line of code.


In just 3 hours and 28 minutes, I built a chat system that feels ready for real users. More importantly, the process reaffirmed my belief that modern development thrives on collaboration—between frameworks, between libraries, and between humans and machines. This synergy heralds a new era where innovation accelerates, and the horizon of what is possible stretches ever farther.

Rabbi Mehedi 🇧🇩

About Rabbi Mehedi 🇧🇩

Professional content creator and member of DHK Creative community.

Comments (0)

Be the first to comment on this article