RouteRunner
Delivery route management for supermarket drivers and managers
Why I built it
I worked as a delivery driver for four years across different companies and kept running into the same problems: routes were planned without accounting for traffic or how long certain addresses actually took, managers had no visibility into whether drivers were falling behind, and systems would consistently underestimate delivery times leaving customers waiting and drivers stressed.
I wanted to build something that actually solved those problems, designed by someone who had experienced them firsthand.
What I built
RouteRunner has two sides: a manager interface for planning routes and a mobile-friendly driver app accessed by scanning a QR code.
Managers can create delivery slots, add orders (addresses are geocoded automatically via Google Maps), and optimise routes using a nearest-neighbour TSP algorithm powered by the Google Distance Matrix API. Stops that won't fit within a time window are flagged and moved automatically. Routes are locked and printed as A4 loading sheets with a QR code in last-in-first-out order so the van is loaded correctly.
Drivers scan the QR code on their phone, get a map with a parking pin and door pin per stop, navigate, photograph proof of delivery, and mark stops done. The app refines parking and door coordinates from real driver GPS over time, so the map becomes more accurate with each delivery.
Production deployment
The app runs on my own VPS behind nginx, served by Gunicorn with four workers, managed as a systemd service so it starts on boot and restarts automatically on failure. HTTPS is handled by Let's Encrypt required because mobile browsers block GPS access on plain HTTP.
The database started as SQLite during development. When I moved to production I hit a problem: SQLite only allows one writer at a time, so multiple Gunicorn workers would collide on concurrent requests. I migrated the schema and data to PostgreSQL to fix this.
Challenges
The scatter graph showing predicted vs actual arrival times updating live as drivers complete stops was the hardest frontend piece. I had to learn enough JavaScript to build it from scratch without a charting library.
Time zones were a persistent problem. The VPS is in Switzerland (CET/CEST) and the drivers are in the UK (GMT/BST). I store everything in UTC and hardcode Europe/London as the display timezone, which works but means the app only runs correctly in the UK. That's the clearest thing I'd redesign now proper per-store timezone configuration rather than a hardcoded constant.