Laravel Sep 18, 2026 · 11 min read

Building TaskFlow: a realtime team task board

A behind-the-scenes look at shipping a shared task board — the schema, drag-and-drop API, and the realtime updates that make a team feel in sync.

Building TaskFlow: a realtime team task board cover

TaskFlow came out of a problem every small team has: a task board that only one person can look at at a time. The goal was a shared Kanban board where dragging a card feels instant for everyone in the room. Here's the story of building it, with the real screens along the way.

The schema at a glance

The whole board hangs off three tables. A board has many lists, and a list has many cards — three migrations, three relationships, and the data model fits on one screen (see the screenshots below):

2026_05_01_000000_create_cards_table.php
Schema::create('cards', function (Blueprint $table) {
    $table->id();
    $table->foreignId('list_id')->constrained()->cascadeOnDelete();
    $table->string('title');
    $table->unsignedInteger('position')->default(0);
    $table->foreignId('assignee_id')->nullable()->constrained('users');
    $table->timestamps();
});

The board

The landing page after login is the board from the first screenshot — three lists rendered left to right, each card fed by a single position column. Drag-and-drop is a small helper that writes the new list and position back to the API; no heavy library, no server re-render, just a PATCH per drop.

The REST API

The frontend talks to a slim set of endpoints — all route-model-bound, all returning consistent JSON shapes so the drag-and-drop code never has to guess:

Form requests keep controllers thin, and APIResource keeps responses shaped — one source of truth for what a card looks like over the wire.

Realtime updates without polling

The magic moment is dragging a card and watching your teammate's screen update on its own. That's a queue, a websocket, and a broadcast event working together:

CardController.php
$card->update($request->validated());

broadcast(new CardMoved($card))
    ->toOthers();

What I'd do differently next time

  • Add optimistic updates earlier. Waiting on the API response for every drop felt sluggish until I applied the move instantly and rolled it back on failure.
  • Version the events. A card payload that changed its shape mid-beta broke the mobile client until I added an event_version field.
  • Put the queue worker in dev up front. I lost an afternoon to realtime events that never fired because no worker was running.

TL;DR

Three tables, one position column, a slim REST API, and a broadcast event for every move. The boring parts — consistent JSON and a single source of truth for card order — were exactly the parts that made it feel realtime.

TaskFlow taught me that "realtime" is less about websockets and more about making small decisions about schema, ordering, and event shape. That's the whole walkthrough — four screens and the decisions behind each one.

Laravel Project Realtime Queues
Share:
FR

Fazale Rabbi

Laravel Full-Stack Developer writing about what I build and debug.

Let's work together →

Keep reading