
One backend, three products, zero fake separation.
WaterDelivery Philippines is a multi-platform application built to support water delivery services across the Philippines. The system includes web and mobile apps for suppliers, riders, and customers.
The project originally started with a legacy CodeIgniter codebase and a dated UI. We were tasked to transition to Laravel, restructuring the backend for maintainability and performance. On the frontend, I led the rebuild of the UI using Vue.js, improving responsiveness and user experience across devices.
For mobile support, we integrated Ionic Capacitor to deploy the same Vue-based frontend as a native-like app on Android and iOS, reducing code duplication while retaining platform-specific features.
Water delivery sounds simple until you map out the actual flow: a customer picks a supplier, places an order, a rider picks it up and delivers it, and the vendor tracks all of it. Each role needs a different screen, different data, different speed. Building one app that tries to serve all three is how you end up with a mess of toggles and hidden tabs. So we didn't — we built three.
- A single backend powering three role-specific applications
- Native-ready Vue and Capacitor apps for customer, rider, and vendor
- End-to-end flows: ordering, fulfillment, payments, messaging, and rewards
- Operational tooling for delivery areas, schedules, and customer management
- Super-admin panel for corporate clients, billing, invoicing, and analytics
My responsibilities included full-stack development, system architecture, and CI/CD setup. The result is a modular platform that continues to scale as more delivery partners come on board.
One order, many surfaces, one source of truth.
Start the animation to follow authentication, ordering, notifications, dispatch, route checks, and fulfillment.
How the platform actually moves.
Role split, not role overload. The customer app creates demand. The supplier app shapes supply. The rider app executes the delivery. The backend holds the truth and pushes the state outward. The customer app handles browsing, checkout, and tracking. This flows into the WD-API which validates and persists data, then into Jobs + Events which notify, sync, and fan out, and finally to the Rider + Vendor Apps which fulfill and manage.












Super-admin where the whole operation is visible.
The three apps handle the day-to-day. The super-admin panel is where everything underneath gets managed. Runs on Backpack for Laravel, extended with custom views and a fully custom analytics layer, living in the same monolith as the API.
Orders + customers
Every order across every supplier in one place. Full preview per order with map, status history, product breakdown, proof of delivery, and payment receipt. Filterable by date, status, schedule, and payment method. Customer management sits alongside it: account details, address book, order history, reward points, and active subscriptions across all suppliers. Admins can cut through without touching the vendor app.

Corporate clients, companies and embassies tied to a preferred supplier, with billing consolidated to the company rather than per employee. Billing and invoicing covers supplier statements, sales invoices, service fees, and potability reports with downloadable PDFs per entry. The service area map plots delivery zones as polygons across Metro Manila so coverage gaps are visible at a glance. Analytics runs in two modes, quick summary or advanced chart breakdowns covering acquisition, retention, CLV, orders by channel, supplier growth, and delivery performance.
A platform that could get sharper without splitting apart.
What the three-app split actually bought us.
Keeping the apps separate meant each one could go deeper on its own job. A single backend made that possible — auth, orders, payments, and messaging were all consistent because they all hit the same API. There was no duplicated logic to keep in sync.
Role-focused UI with zero feature-flag debt. Three separate apps meant each role got a focused interface — no hidden tabs, no "this section isn't for you" moments. The customer app never had to expose supplier config it didn't need. The rider app cut down to just the screens that mattered during a run.
One fix lands everywhere. Auth, ordering, notifications, payments, and finance all lived in one backend. A bug fix or a feature shipped once and landed in all three apps simultaneously — no coordinating releases across separate API versions.
Payments needed a two-part fix. The original flow opened Xendit in an external browser, killing the WebSocket connection before payment confirmation could land back in the app. Switching to an in-app browser kept the session alive, and a backend WebSocket listener catches the Xendit webhook and pushes the success state via Ably. By the time the customer closes the payment screen, the order is already confirmed.
The backend became more than an API. Services, jobs, observers, reporting, admin tooling, and a full billing layer all lived in the same monolith. The migration from CodeIgniter to Laravel forced a full rethink of the data model — painful, but the reason the platform didn't buckle when the feature list grew to 295 routes and 112 models.
This is the project I'd show someone if they asked what I actually know how to do. It covers the full stack end-to-end — backend architecture, mobile-ready frontend, realtime features, payments, and CI/CD. It's also where I learned that the hardest decisions aren't technical. They're about scope.