Case Study·

Why We Don't Build Chat From Scratch (And Neither Should You)

In-app messaging sounds like a simple feature, but building it from scratch is a project on its own. Here's why we use managed chat services and how we integrated one in a single sprint.
Why We Don't Build Chat From Scratch (And Neither Should You)

"We need a chat feature." Five words that sound simple but hide a massive engineering project underneath. We hear this request regularly from clients who are building platforms where users need to communicate. And every time, our answer is the same: we don't build chat from scratch. Here's why.

The request that sounds simple

A client comes to us with a platform idea. Users need to message each other. Maybe it's artists talking to managers, editors talking to sound designers, or customers talking to support. The feature request fits in one sentence: "Add a chat."

From the outside, it looks straightforward. A text input, a send button, messages appearing on screen. How hard can it be?

What building chat actually means

Very hard. Real-time messaging is one of those features that looks like 10% of the work but turns into 60% if you try to build it yourself. Here's what's hiding behind that simple chat bubble:

WebSocket Infrastructure
Persistent connections, reconnection logic, heartbeats, connection state management across devices.
Delivery Guarantees
Message ordering, deduplication, retry logic. What happens when the network drops mid-message?
Message Storage
History, search, pagination, attachments. Every message needs to be stored, indexed, and retrievable.
Push Notifications
Mobile push, desktop notifications, badge counts. Different platforms, different APIs.
Read Receipts & Presence
Who's online? Who read the message? When? This needs real-time sync across all connected clients.
Scaling
What works for 10 users breaks at 1,000. What works at 1,000 breaks at 100,000.

Companies like Slack, WhatsApp, and Discord have entire engineering teams dedicated to messaging infrastructure. That's their core product. When chat is just one feature in your app, spending months building and maintaining this infrastructure doesn't make sense.

Building real-time chat from scratch typically takes 3-6 months of dedicated engineering time. That's time and budget taken away from the features that make your product unique.

The smart approach: managed chat services

Services like Stream, Sendbird, and PubNub exist specifically to solve this problem. They provide battle-tested messaging infrastructure through APIs and SDKs. You get years of engineering in a single integration.

What a managed service gives you out of the box:

  • Real-time message delivery with offline support
  • Message history, search, and threading
  • Read receipts and typing indicators
  • Unread counts and push notifications
  • File and image attachments
  • Moderation and content filtering
  • SDKs for web, iOS, Android, Flutter, React Native

The cost? A predictable monthly fee based on usage, instead of months of custom development and ongoing maintenance.

How we did it: The Artist Suite

We recently built The Artist Suite, a platform connecting music artists with industry professionals. One of the core requirements was direct messaging between users. Artists needed to talk to managers, producers, and collaborators directly inside the app.

The stack

  • Backend: Django with Django REST Framework
  • Frontend: Nuxt 4 (Vue 3) with SSR
  • Chat provider: Stream Chat

The integration

The entire chat feature was shipped in one sprint (two weeks). Here's how it works:

Backend (3 endpoints, ~100 lines of code):

The Django backend handles authentication and channel management. Stream never sees your users' passwords. The backend generates short-lived tokens and manages channel creation.

views.py
class TokenView(APIView):
    """Generate a Stream Chat token for the authenticated user."""

    def post(self, request):
        client = StreamChat(
            api_key=settings.STREAM_API_KEY,
            api_secret=settings.STREAM_API_SECRET,
        )
        # Upsert user info to Stream
        client.upsert_user({"id": str(request.user.id), "name": request.user.name})
        token = client.create_token(str(request.user.id))
        return Response({"token": token, "api_key": settings.STREAM_API_KEY})
views.py
class CreateChannelView(APIView):
    """Create or get a messaging channel between two users."""

    def post(self, request):
        other_user_id = request.data["user_id"]
        # Deterministic channel ID from sorted user IDs
        members = sorted([str(request.user.id), str(other_user_id)])
        channel_id = f"chat-{members[0]}-{members[1]}"

        client = StreamChat(
            api_key=settings.STREAM_API_KEY,
            api_secret=settings.STREAM_API_SECRET,
        )
        channel = client.channel("messaging", channel_id, {"members": members})
        channel.create(str(request.user.id))
        return Response({"channel_id": channel_id})
The deterministic channel ID pattern (chat-{user1}-{user2} with sorted IDs) means you always get the same channel for the same pair of users, no matter who initiates the conversation.

Frontend (Vue composable + component):

The frontend connects to Stream using the token from the backend. All real-time updates, message rendering, and state management happen through Stream's JavaScript SDK.

inbox.service.ts
export async function inboxGetToken(): Promise<{ token: string; api_key: string }> {
  return await $api(endpoints.token, { method: "POST" })
}

export async function inboxCreateChannel(userId: string): Promise<{ channel_id: string }> {
  return await $api(endpoints.channel, {
    method: "POST",
    body: { user_id: userId },
  })
}

The chat component listens for message.new and message.read events from Stream, updates the UI in real time, and tracks unread counts per conversation.

No database models needed. Stream stores all messages, handles delivery, and manages state. The Django backend has zero chat-related database tables.

What we shipped in one sprint

Direct Messaging
1-to-1 conversations between any two users on the platform.
Read Receipts
Message delivery and read status, updated in real time.
Unread Counts
Badge counts per conversation and a global unread indicator.
User Roster
List of all available users with online/offline status.
Keyboard Navigation
Arrow keys to switch between conversations for power users.
Responsive UI
Split-panel on desktop, slideover on mobile. Same functionality everywhere.

When does this approach make sense?

This build-vs-buy decision is clear-cut in most cases:

Use a managed service when:

  • Chat is a feature in your app, not the core product
  • You need it reliable and production-ready from day one
  • Your engineering budget should go toward what makes your product unique
  • You expect to scale without rebuilding infrastructure

Consider building custom when:

  • Messaging IS your core product (you're building the next Slack)
  • You have very specific compliance requirements that no provider can meet
  • You have a dedicated team to maintain it long-term

For most platforms we build at MusicTech Lab, the choice is obvious. Our clients' budgets should go toward the features that set their product apart, not toward reinventing infrastructure that already exists.

Key Takeaways

Chat Is Not a Simple Feature
Real-time messaging involves WebSockets, delivery guarantees, storage, push notifications, and scaling. It's a product, not a feature.
Buy, Don't Build
Services like Stream give you years of engineering through a single API. Your budget should go toward what makes your product unique.
One Sprint Integration
We shipped direct messaging with read receipts, unread counts, and responsive UI in two weeks using Stream Chat + Django + Nuxt.
Zero Database Overhead
No chat-related tables in your database. Stream handles all message storage, delivery, and state management.

The bottom line

Chat is one of those features where the gap between "looks simple" and "is simple" is enormous. A managed service like Stream closes that gap. You get a production-grade messaging system integrated in days, not months.

We've used this approach across multiple projects, and it works every time. The client gets real-time messaging that just works. We get to spend our time on the features that actually matter for their business.


Planning an app with real-time features like chat, notifications, or live updates? Let's talk about the right building blocks for your project.

Need Help with This?

Building something similar or facing technical challenges? We've been there.

Let's talk — no sales pitch, just honest engineering advice.