Case Study
Zirak — real-time car sharing, built solo
The problem
A car-sharing service lives or dies on two things: riders need to see where cars are right now, and two people must never book the same car for the same slot. HTTP polling gives you stale positions and a chatty mobile client. A naive booking flow gives you double bookings the first week traffic grows.
Constraints
- One developer, me, owning backend, infrastructure, and deployment.
- A client budget that ruled out managed real-time services like Pusher; the platform ran on a dedicated server I provisioned and deployed myself.
- Mobile clients on unreliable networks, so connections drop and reconnect constantly.
What I built
One Laravel application owns the whole platform: accounts, vehicles, bookings, payments, and the admin panel. For the real-time side I stayed inside the framework and used Laravel's broadcasting layer: clients subscribe to channels and receive live vehicle positions and booking state changes over WebSocket.
For the WebSocket server itself I started with beyondcode/laravel-websockets, a self-hosted Pusher-compatible server, which kept the real-time infrastructure on our own machine instead of a metered third-party service.
The SQL database stays the single source of truth. The broadcast layer never owns state. It fans out changes, so a dropped connection costs a client nothing but a resync on reconnect. A database transaction settles each booking conflict, which is what keeps two riders from winning the same car.
The migration to Reverb
When Laravel shipped Reverb as its first-party WebSocket server, I migrated Zirak off laravel-websockets, which had gone quiet. The app only ever talked to the broadcasting abstraction, never to the server behind it, so the migration touched configuration and infrastructure while channels, events, and clients kept working through the swap.
Decisions worth defending
One framework end to end. As a solo developer, a single Laravel codebase meant one deploy, one queue, one mental model. Adding a second runtime for WebSockets would have doubled the operational surface for a feature the Laravel ecosystem already handled.
Self-hosted over managed. A Pusher-compatible server on a dedicated machine kept costs flat as connections grew, at the price of operating the server myself. At this budget that was the right trade.
Buying into the abstraction. Writing against Laravel's broadcasting contract instead of a specific server is what made the Reverb migration cheap. Nothing in the product code knew which server sat behind it, so replacing the server cost a config change.
Outcome
Zirak shipped with live tracking and booking working end to end (zirak.info). I designed, built, and deployed the platform, dedicated server included, then handed it off. I no longer maintain it.