Skip to main content

How Chorus Works

Laravel Chorus creates a bidirectional synchronization system between your Laravel backend and client applications. This page explains the core architecture and data flow patterns that make real-time synchronization possible.

Architecture Overview

Core Components

Server-Side Components

Applied to Laravel models to enable synchronization. Defines which fields to sync and provides filtering capabilities.
class Message extends Model
{
    use Harmonics;
    
    protected $syncFields = ['id', 'body', 'user_id', 'created_at'];
    
    protected function syncFilter(): Builder
    {
        return $this->where('user_id', auth()->id());
    }
}
Central change log that records all create, update, and delete operations for tracked models.
ColumnPurpose
table_nameWhich model table changed
record_idPrimary key of the changed record
operationType of change (create/update/delete)
dataJSON snapshot of the changed fields
user_idFor user-scoped synchronization
rejectedBoolean flag that is true if the user write is rejected
rejected_reasonReason for rejection (string)
Handle client write operations with validation, business logic, and conflict resolution.
class CreateMessageAction extends WriteAction
{
    public function handle(Request $request, array $data): Message
    {
        return Message::create([
            'id' => $data['id'] ?? Str::uuid(),
            'body' => $data['message'],
            'user_id' => auth()->id(),
        ]);
    }
    
    public function rules(): array
    {
        return [
            'message' => 'required|string|max:255'
        ];
    }
}
WebSocket server that broadcasts change events to connected clients in real-time.

Client-Side Components

Browser-based database that stores synchronized data locally for offline access and instant queries. We use DexieJS to provide to sensibly interface with IndexedDB.Example DexieJS Query
const messagesQuery = useTableQuery<Message>(
    (table) => selectedPlatform
        ? table.where('platform_id').equals(selectedPlatform)
        : table,
        [selectedPlatform]
);
React hooks that manage synchronization, provide optimistic updates, and handle real-time data binding.
const { data: messages, create, update, delete: remove } = useTable(
  'messages', 
  types
);
Persistent connection to Laravel Reverb for receiving real-time updates and broadcasting changes.
Immediate UI updates before server confirmation, providing instant user feedback.

Change Detection Adapters

Chorus supports multiple methods for detecting model changes:

1. Eloquent (Default)

// Automatically triggers when models are saved/deleted
public static function bootHarmonics()
{
    static::created(fn($model) => self::createHarmonic($model, 'create'));
    static::updated(fn($model) => self::createHarmonic($model, 'update'));
    static::deleted(fn($model) => self::createHarmonic($model, 'delete'));
}

2. Postgres: WAL Logical Replication Stream (Future Enhancement)

It is planned to add support for Postgres Logical Replication stream. For Postgres databases with logical replication enabled, it is possible to listen to the database’s logical changes (create, update, delete) directly. This means that we can mutate the database directly without going through the Eloquent ORM. This approach is ideal for applications that have databases that can change outside Laravel in some way.

Synchronization Strategies

Selective Field Sync

Only specified fields are synchronized, reducing bandwidth and storage requirements.
protected $syncFields = [
    'id',           // Always include primary key
    'title',        // User-visible content
    'status',       // Business logic fields
    'updated_at'    // Timestamp for conflict resolution
    // 'password' excluded for security
    // 'internal_notes' excluded to reduce size
];

Filtered Synchronization

Apply custom logic to determine which records each user should receive.
protected function syncFilter(): Builder
{
    $user = auth()->user();
    
    return $this->where(function($query) use ($user) {
        $query->where('user_id', $user->id)              // Own records
              ->orWhere('assigned_to', $user->id)         // Assigned records
              ->orWhere('team_id', $user->team_id);       // Team records
    });
}

Channel-based Broadcasting

Different WebSocket channels for different data scopes and user permissions.
// User-specific channel
"chorus.user.{$userId}"

// Tenant-specific channel  
"chorus.tenant.{$tenantId}.user.{$userId}"

// Table-wide updates
"chorus.table.messages"

// Record-specific updates
"chorus.record.messages.{$messageId}"

Conflict Resolution

When multiple clients modify the same data simultaneously, Chorus handles conflicts using:

Last-Write-Wins Strategy

  • Server timestamps determine the “winning” version
  • Client changes are overwritten by server updates
  • Simple but may lose user edits in rare cases

Optimistic Locking

  • Version numbers or timestamps prevent conflicting updates
  • Failed updates trigger user notifications
  • Allows manual conflict resolution

Field-Level Merging

  • Different fields can be updated by different users
  • Complex but preserves maximum user input
  • Requires careful schema design

Performance Optimizations

Delta Compression

Only changed fields are transmitted, not entire records

Batch Processing

Multiple changes combined into single operations

Connection Pooling

Efficient WebSocket connection management

Local Caching

IndexedDB provides instant query performance

Security Considerations

  • Data Filtering: syncFilter() ensures users only receive authorized data
  • Write Validation: All client operations validated server-side
  • Channel Authorization: WebSocket channels require proper authentication
  • Field Selection: Sensitive fields excluded from synchronization
  • Tenant Isolation: Automatic multi-tenant data separation

Next: Learn how to implement Chorus in your application with our Getting Started guide.