The problem
Booking a safari usually means emailing several operators, waiting for quotes and trying to compare packages that are written in different ways. Park fees, the biggest fixed cost, are often hidden inside a single price. Travellers cannot tell a fair quote from an inflated one.
FindMySafari puts the itineraries, the fees and the operators side by side, so a traveller can see what a trip should cost before they ask anyone for a quote.
What I built
- A searchable catalogue of 208 safari itineraries with day-by-day plans, filters by destination, month, group size and budget, saved trips and side-by-side comparison.
- Price indexes for Kenya and Tanzania that show what a safari typically costs by length and style, alongside the official park fees.
- Park maps drawn from real protected-area boundary data.
- One enquiry, matched to operators. A traveller fills in one form. Matching ranks operators by how well they fit the trip, and the traveller chooses up to four to receive it.
- Portals for everyone involved. Travellers get an account with their enquiries and saved trips. Operators sign up, pass verification with company documents and two-factor sign-in, and manage their itineraries. An admin area handles verification, routing and publishing, with an audit log.
How it is built
The site runs on Next.js 16 with a Postgres database designed in Drizzle (106 tables covering the catalogue, operators, enquiries, bookings and a double-entry ledger). Redis handles caching and job queues, and media sits in S3-compatible storage. Everything runs in Docker on a VPS I manage.
I migrated the original WordPress site into this build without losing search traffic: every one of the 2,245 live URLs was crawled first and checked against the new build, so titles, descriptions and canonical URLs match.
Bookings and payouts are designed in from the start. The ledger, quotes, deposits, refunds and payment adapters for Paystack, Flutterwave and Pesapal are built and tested in a sandbox, ready to switch on once provider testing is complete.
