Engineering·

How We Built Real-Time Notifications with WebSockets

A behind-the-scenes look at how CraftDesk delivers instant notifications to your browser.

One of the things that makes CraftDesk feel responsive is real-time notifications. When a teammate comments, assigns a task, or updates a project, you know about it instantly—no refresh needed. Today, we're pulling back the curtain on how we built this.

The Challenge: Never Polling Again

Before real-time, teams had two bad options:

  1. Polling – Your browser asks "anything new?" every few seconds. Wasteful, expensive, creates server load, feels sluggish.
  2. Email – Notifications arrive minutes later, pile up, get lost.

Neither option respects your attention or your infrastructure budget. We wanted instant delivery without hammering our database. That meant WebSockets.

Our Stack

We're using Laravel Reverb on the backend and Laravel Echo on the frontend. Here's why:

  • Reverb is a WebSocket server purpose-built for Laravel. It handles connection management, authentication, and broadcasting seamlessly. Since our entire backend is Laravel, it felt natural.
  • Echo is the client-side library that connects to Reverb and subscribes to channels. It's event-driven and elegant.
  • Sanctum handles WebSocket authentication. Users authenticate once, and their session token is valid for WebSocket connections.

No external services, no vendor lock-in. Everything runs on our infrastructure.

Architecture: Channels and Broadcasting

Every CraftDesk workspace has a private channel:

private-workspace.{workspaceId}

Only authenticated users with access to that workspace can subscribe. Reverb validates permissions on subscription—so even if someone guesses a channel name, they can't listen in.

When something happens (a comment, an assignment, a status change), we broadcast an event to that channel:

// In Laravel, when a comment is created:
broadcast(new CommentCreated($comment))->toOthers();

All connected clients receive it instantly. No database queries needed—it's a straight event flow.

Frontend: Listening for Events

On the Nuxt side, Echo sets up a listener:

import { useBroadcaster } from '@/composables/useBroadcaster'

export default {
  setup() {
    const broadcaster = useBroadcaster()

    onMounted(() => {
      broadcaster.listen('private-workspace.123', 'CommentCreated', (event) => {
        // Update your local state, re-fetch data, show notification
        console.log('New comment:', event.comment)
      })
    })

    return {}
  }
}

When the event arrives, we update the local component state. If it's a major change (like a new comment), we might trigger a re-fetch of that task. For notifications, we show a toast or badge.

Performance Considerations

WebSocket connections are persistent, which is great for real-time but requires careful resource management:

  • Connection pooling – Each client gets one WebSocket to Reverb, shared across all channels they subscribe to. One socket, unlimited channels.
  • Selective subscription – We don't subscribe to channels we're not viewing. Switch to a different project? Unsubscribe from the old one, subscribe to the new one.
  • Memory pressure – Keeping thousands of connections open requires monitoring. We use Reverb's built-in metrics to track concurrent connections and scale horizontally when needed.
  • Graceful degradation – If WebSocket drops (network hiccup), Echo automatically reconnects. The user doesn't notice.

What Gets Broadcast

We broadcast events for:

  • Comments and replies – Discussion updates in real-time.
  • Task assignments and status changes – Team coordination without polling.
  • Subscription events – When a user's subscription changes, they're notified immediately.
  • Quota warnings – If a team is approaching API limits, they get a real-time alert.
  • Team changes – When members are added or removed, everyone sees it instantly.

Each event type is a separate Laravel event class, making it easy to add new notifications as features evolve.

What's Next

We're experimenting with:

  • Typing indicators – "Alice is typing..." updates in real-time, like Slack.
  • Cursor positions – Collaborative editing features that show where each team member is.
  • Activity feed compression – When many events happen at once, bundling them to avoid notification spam.
  • Selective delivery – Let users choose which event types trigger notifications, reducing noise.

The Takeaway

Real-time doesn't require complexity. By choosing tools purpose-built for the job (Reverb, Echo, Sanctum), we built something that feels instant while remaining maintainable and scalable. Your team gets responsive, distraction-free collaboration.

If you're building real-time features on Laravel, we recommend the same stack. It's solid, well-documented, and battle-tested.

Onward to faster feedback loops.

The CraftDesk Team