Vous etes-vous deja demande comment les entreprises suivent des milliers d'expeditions en temps reel ? Ou comment les plateformes de trading mettent a jour les prix instantanement sur des milliers d'ecrans ? J'ai construit ces systemes a grande echelle, traitant plus de 2 milliards de dollars en transactions annuelles. Aujourd'hui, je vais vous montrer exactement comment construire un tableau de bord temps reel pret pour la production avec Laravel, WebSockets et Vue.js.
Ce n'est pas de la theorie - ce sont des patterns eprouves de vrais systemes de production gerant des operations logistiques 24/7.
Ce que nous construisons
Nous allons creer un tableau de bord logistique temps reel qui :
- Suit les emplacements des expeditions avec des mises a jour en direct
- Affiche des metriques temps reel (livraisons, revenus, performance)
- Implemente un systeme de verrouillage pour prevenir les editions conflictuelles
- Gere des milliers de connexions simultanees
- Scale horizontalement avec Redis pub/sub
Voici a quoi ressemble le tableau de bord final :
- Plusieurs utilisateurs voient les mises a jour instantanement
- Les statuts d'expedition changent en temps reel
- Les metriques se mettent a jour sans rafraichissement de page
- Les verrous d'edition empechent les conflits de donnees
Prerequis
Vous aurez besoin de :
- PHP 8.1+ avec Laravel 10+
- Node.js 18+ pour Vue 3
- Redis (ou KeyDB pour de meilleures performances)
- Connaissances de base de Laravel et Vue
- Composer et npm installes
Partie 1 : Configurer le backend Laravel
Etape 1 : Creer le projet Laravel
composer create-project laravel/laravel realtime-logistics
cd realtime-logistics
# Installer les dependances de broadcasting et WebSocket
composer require pusher/pusher-php-server
composer require predis/predis
# Installer le package Laravel WebSockets (alternative auto-hebergee a Pusher)
composer require beyondcode/laravel-websocketsEtape 2 : Configurer le broadcasting
Mettez a jour votre fichier .env :
BROADCAST_DRIVER=pusher
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
# Utiliser Laravel WebSockets comme remplacement de Pusher
PUSHER_APP_ID=local-app
PUSHER_APP_KEY=local-key
PUSHER_APP_SECRET=local-secret
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
PUSHER_APP_CLUSTER=mt1
# Configuration Redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379Etape 3 : Schema de base de donnees
Creez les migrations pour notre systeme logistique :
php artisan make:migration create_shipments_table
php artisan make:migration create_metrics_table
php artisan make:migration create_edit_locks_table// database/migrations/create_shipments_table.php
Schema::create('shipments', function (Blueprint $table) {
$table->id();
$table->string('tracking_number')->unique();
$table->string('origin');
$table->string('destination');
$table->enum('status', ['pending', 'in_transit', 'delivered', 'exception']);
$table->decimal('latitude', 10, 7)->nullable();
$table->decimal('longitude', 10, 7)->nullable();
$table->decimal('value', 10, 2);
$table->json('metadata')->nullable();
$table->timestamps();
$table->index(['status', 'created_at']); // Optimise pour les requetes dashboard
$table->index(['latitude', 'longitude']); // Requetes spatiales
});
// database/migrations/create_metrics_table.php
Schema::create('metrics', function (Blueprint $table) {
$table->id();
$table->string('key')->unique();
$table->decimal('value', 15, 2);
$table->string('unit')->nullable();
$table->timestamp('calculated_at');
$table->timestamps();
$table->index('calculated_at');
});
// database/migrations/create_edit_locks_table.php
Schema::create('edit_locks', function (Blueprint $table) {
$table->id();
$table->morphs('lockable'); // Polymorphique - peut verrouiller n'importe quel modele
$table->unsignedBigInteger('user_id');
$table->timestamp('locked_at');
$table->timestamp('expires_at');
$table->index(['lockable_type', 'lockable_id', 'expires_at']);
});Etape 4 : Creer les modeles
// app/Models/Shipment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use App\Traits\Lockable;
use App\Events\ShipmentUpdated;
class Shipment extends Model
{
use HasFactory, Lockable;
protected $fillable = [
'tracking_number', 'origin', 'destination',
'status', 'latitude', 'longitude', 'value', 'metadata'
];
protected $casts = [
'metadata' => 'array',
'value' => 'decimal:2',
'latitude' => 'decimal:7',
'longitude' => 'decimal:7',
];
protected $dispatchesEvents = [
'updated' => ShipmentUpdated::class,
];
// Optimisation production : Cache les expeditions frequemment accedees
public static function findByTrackingCached($trackingNumber)
{
return Cache::remember(
"shipment.{$trackingNumber}",
now()->addMinutes(5),
fn() => self::where('tracking_number', $trackingNumber)->first()
);
}
// Requete geospatiale pour les expeditions proches
public function scopeNearby($query, $lat, $lng, $radiusKm = 50)
{
// Formule Haversine pour le calcul de distance
$haversine = "(
6371 * acos(
cos(radians(?)) * cos(radians(latitude)) *
cos(radians(longitude) - radians(?)) +
sin(radians(?)) * sin(radians(latitude))
)
)";
return $query
->selectRaw("*, {$haversine} AS distance", [$lat, $lng, $lat])
->having('distance', '<=', $radiusKm)
->orderBy('distance');
}
}Etape 5 : Implementer le trait Lockable
Ceci empeche les editions conflictuelles - crucial pour les donnees financieres :
// app/Traits/Lockable.php
namespace App\Traits;
use App\Models\EditLock;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
trait Lockable
{
public function locks()
{
return $this->morphMany(EditLock::class, 'lockable');
}
public function acquireLock($userId = null, $duration = 300)
{
$userId = $userId ?? Auth::id();
// Verifier les verrous valides existants
$existingLock = $this->locks()
->where('expires_at', '>', now())
->where('user_id', '!=', $userId)
->first();
if ($existingLock) {
return false; // Quelqu'un d'autre a le verrou
}
// Nettoyer les verrous expires
$this->locks()->where('expires_at', '<=', now())->delete();
// Creer un nouveau verrou
return $this->locks()->create([
'user_id' => $userId,
'locked_at' => now(),
'expires_at' => now()->addSeconds($duration),
]);
}
public function releaseLock($userId = null)
{
$userId = $userId ?? Auth::id();
return $this->locks()
->where('user_id', $userId)
->delete();
}
public function isLocked($excludeUserId = null)
{
$query = $this->locks()->where('expires_at', '>', now());
if ($excludeUserId) {
$query->where('user_id', '!=', $excludeUserId);
}
return $query->exists();
}
public function currentLock()
{
return $this->locks()
->where('expires_at', '>', now())
->with('user')
->first();
}
}Etape 6 : Creer les evenements de broadcasting
// app/Events/ShipmentUpdated.php
namespace App\Events;
use App\Models\Shipment;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ShipmentUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $shipment;
public function __construct(Shipment $shipment)
{
$this->shipment = $shipment;
}
public function broadcastOn()
{
return [
new Channel('shipments'),
new Channel("shipment.{$this->shipment->id}"),
];
}
public function broadcastWith()
{
return [
'id' => $this->shipment->id,
'tracking_number' => $this->shipment->tracking_number,
'status' => $this->shipment->status,
'latitude' => $this->shipment->latitude,
'longitude' => $this->shipment->longitude,
'value' => $this->shipment->value,
'updated_at' => $this->shipment->updated_at->toIso8601String(),
];
}
// Optimisation de performance : Utiliser une queue pour le broadcasting
public function broadcastQueue()
{
return 'broadcasts';
}
}Partie 2 : Frontend Vue.js avec mises a jour temps reel
Etape 8 : Installer Vue et les dependances
npm install vue@3 @vitejs/plugin-vue
npm install laravel-echo pusher-js
npm install axios pinia @vueuse/core
npm install -D @types/laravel-echoEtape 9 : Configurer Vite pour Vue
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
});Etape 10 : Configurer Laravel Echo
// resources/js/bootstrap.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
wsHost: import.meta.env.VITE_PUSHER_HOST ?? window.location.hostname,
wsPort: import.meta.env.VITE_PUSHER_PORT ?? 6001,
wssPort: import.meta.env.VITE_PUSHER_PORT ?? 6001,
forceTLS: false,
encrypted: false,
disableStats: true,
enabledTransports: ['ws', 'wss'],
});Partie 3 : Optimisation Redis et scaling
Service temps reel avec Redis Pub/Sub
// app/Services/RealtimeService.php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
class RealtimeService
{
private $redis;
public function __construct()
{
$this->redis = Redis::connection('pubsub');
}
/**
* Publier la mise a jour a tous les serveurs connectes
*/
public function publishShipmentUpdate($shipment)
{
$channel = "shipment.updates.{$shipment->status}";
$data = [
'id' => $shipment->id,
'tracking_number' => $shipment->tracking_number,
'status' => $shipment->status,
'location' => [
'lat' => $shipment->latitude,
'lng' => $shipment->longitude,
],
'timestamp' => now()->toIso8601String(),
'server_id' => config('app.server_id'),
];
$this->redis->publish($channel, json_encode($data));
$this->redis->publish('shipment.updates.all', json_encode($data));
}
}Insights production et conseils eprouves
Optimisation de performance
-
Indexation de base de donnees : Les index composites sur
(status, created_at)et(latitude, longitude)sont cruciaux. Sans eux, les requetes dashboard ralentiront au-dela de 100k expeditions. -
Gestion memoire Redis : Nous avons appris a nos depens - toujours definir
maxmemoryet utiliser l'eviction LRU. Une fuite memoire a fait tomber la production pendant 20 minutes. -
Priorites de queues : Des queues separees pour les broadcasts empechent les mises a jour orientees utilisateur d'etre bloquees par les jobs en arriere-plan. Cela a reduit notre latence P95 de 60%.
Strategies de scaling
-
Scaling horizontal : Le pattern Redis pub/sub permet d'executer plusieurs serveurs Laravel. Nous executons 6 serveurs gerant 10k connexions simultanees.
-
Distribution geographique : Deployez les serveurs WebSocket dans plusieurs regions. Utilisez Redis Sentinel pour le failover automatique.
-
Rate limiting : Implementez des limites de messages WebSocket par utilisateur. Le script d'un client a un jour envoye 50k mises a jour/seconde et fait crasher le systeme.
Pieges courants a eviter
- Ne pas broadcaster de donnees sensibles : Toujours filtrer ce qui va au frontend
- Implementer des heartbeats : Detecter les connexions obsoletes et les nettoyer
- Utiliser les transactions de base de donnees : Surtout lors de la mise a jour d'enregistrements lies
- Cache agressif : Mais invalidation strategique
- Planifier la reconnexion : Les clients se deconnecteront - gerez-le gracieusement
Conclusion
Ce pattern de tableau de bord temps reel a fait ses preuves en production, gerant des millions de mises a jour quotidiennes. La combinaison du backend elegant de Laravel, du frontend reactif de Vue et du pub/sub ultra-rapide de Redis cree un systeme a la fois performant et maintenable.
Points cles :
- Concevez pour l'echelle des le premier jour - C'est plus difficile de refactorer plus tard
- Separez les preoccupations - Broadcasts, queues et appels API devraient utiliser differents canaux
- Surveillez tout - Vous ne pouvez pas optimiser ce que vous ne mesurez pas
- Testez avec des donnees realistes - 10 enregistrements de test ne reveleront pas les problemes de performance
Les patterns montres ici ne sont pas que theoriques - ils tournent en production en ce moment, suivant de vraies expeditions valant de l'argent reel.
Maitriser les fondamentaux Laravel d'abord
Construire des tableaux de bord temps reel necessite une solide connaissance de Laravel. Si vous etes nouveau sur Laravel, commencez par ces tutoriels complets :
- Construire un blog avec Laravel - Apprenez Eloquent, l'authentification et les operations CRUD
- Construire un portfolio avec Laravel - Maitrisez les uploads de fichiers, les relations et les panneaux d'admin
- Construire un e-commerce avec Laravel - Patterns avances incluant les queues et la gestion des evenements
Chaque tutoriel inclut des prompts assistes par IA pour vous guider dans la construction d'applications prates pour la production a partir de zero.
Fred
AUTHORFull-stack developer with 10+ years building production applications. I write about cloud deployment, DevOps, and modern web development from real-world experience.
Need a developer who gets it?
POC builds, vibe-coded fixes, and real engineering. Let's talk.
Hire Me →
