A modern, real-time collaborative emoji grid showcasing Laravel 12, React 19, Inertia.js, and Laravel Reverb broadcasting capabilities. This demo demonstrates best practices for building real-time collaborative applications with Laravel.
This application is designed to be deployed on Laravel Cloud. Youโll need to provision:
Note: While the grid itself uses cache storage, a database is still required to prevent errors in Laravelโs session and cache drivers. The database cache driver writes to a
cachetable created by migrations.
toOthers() to prevent duplicate updatesBrowser โ React Component โ Axios โ Laravel Controller โ
Cache Update โ Event Dispatch โ Reverb WebSocket โ
All Other Browsers (Echo) โ UI Update
Key Broadcasting Patterns:
toOthers() Pattern - Broadcasts exclude the originating userX-Socket-ID header for proper toOthers() behaviorLocal Development:
Production (Laravel Cloud):
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate
Option A: One Command (Recommended)
composer run dev
This starts:
Option B: Manual Start (3 terminals)
# Terminal 1: Laravel
php artisan serve
# Terminal 2: Vite
npm run dev
# Terminal 3: Reverb
php artisan reverb:start
Visit http://localhost:8000 (or the port shown in your terminal)
app/
โโโ Broadcasting/
โ โโโ GridPresence.php # Presence channel authorization
โโโ Events/
โ โโโ GridCellClicked.php # Shake animation event
โ โโโ GridCellUpdated.php # Cell emoji update event
โ โโโ GridRainStarted.php # Celebration rain event
โ โโโ UserCountUpdated.php # Active user count event
โโโ Http/
โ โโโ Controllers/
โ โ โโโ GridController.php # Main grid logic
โ โโโ Middleware/
โ โโโ TrackActiveUsers.php # Session-based user tracking
โโโ Services/
โโโ UserPresenceService.php # User presence management
resources/
โโโ js/
โ โโโ app.tsx # Axios + Echo configuration
โ โโโ pages/
โ โโโ Grid.tsx # React grid component
โโโ css/
โโโ app.css # Tailwind styling
routes/
โโโ web.php # Web routes
โโโ channels.php # Broadcasting channel definitions
resources/js/app.tsx)// Automatically attach Socket ID to Axios requests
axios.interceptors.request.use((config) => {
if (window.Echo && typeof window.Echo.socketId === 'function') {
config.headers['X-Socket-ID'] = window.Echo.socketId();
}
return config;
});
toOthers() (app/Http/Controllers/GridController.php)// Broadcast to everyone except the person who clicked
broadcast(new GridCellUpdated([...]))->toOthers();
broadcast(new GridCellClicked(...))->toOthers();
resources/js/pages/Grid.tsx)// Update local state immediately for instant feedback
setCells((prev) => ({ ...prev, [position]: newEmoji }));
// Then send to server
await axios.put(`/grid/${position}`, { click: true });
User Clicks Cell
โ
1. Optimistic UI Update (instant)
โ
2. Axios PUT with X-Socket-ID header
โ
3. Laravel updates cache + click count
โ
4. Broadcast GridCellUpdated โtoOthers()
โ
5. Broadcast GridCellClicked โtoOthers()
โ
6. Check if grid is uniform (rain trigger)
โ
7. Other browsers receive via WebSocket
โ
8. React updates with animations
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
config/broadcasting.php)Already configured for Reverb! No changes needed.
# Open app in 2 browser tabs
# Click a cell in tab 1
# Should appear instantly in tab 2 with animations
# Open app in multiple tabs/browsers
# Watch center display update with "X artisans online"
# Close tabs and see count decrease after ~5 minutes
# Click all 96 clickable cells (excluding center 4) to the same emoji
# Watch the emoji rain celebration trigger!
# Click various cells to add emojis
# Refresh the page
# All emojis and click counts should persist
php artisan cache:clear
# or
php artisan tinker
>>> Cache::forget('emoji_grid')
>>> Cache::forget('emoji_grid_click_counts')
>>> Cache::forget('emoji_grid_timestamps')
>>> Cache::forget('active_users')
| Problem | Solution |
|---|---|
| Emojis not persisting | Run php artisan migrate for cache table |
| Real-time not working | Verify php artisan reverb:start is running |
| User count not updating | Check middleware is registered in bootstrap/app.php |
| Animations feel delayed | Ensure toOthers() is used in broadcasts |
| WebSocket connection fails | Check BROADCAST_CONNECTION=reverb in .env |
| Axios errors | Verify CSRF token meta tag in app.blade.php |
| Log errors (no database) | Database is required even with cache - run migrations |
Why Database is Required:
Even though the grid uses cache for storage, Laravel requires a database for:
Without a database, you may see errors in logs related to sessions and cache operations, even if the application appears to work correctly.
web.php (not api.php)toOthers() to prevent duplicate updatesX-Socket-ID headerThis demo is perfect for learning:
toOthers()MIT
Built as a comprehensive demo for Laravel Reverb WebSocket broadcasting with Inertia.js and React.
Status: โ
Production Ready
Last Updated: November 2, 2025
Framework Versions: Laravel 12, React 19, Inertia 2, Reverb 1
Happy coding! ๐