← All work

Revv

Renting a car in Lebanon meant phone calls, cash, and no way to verify who you were dealing with.

Role
Sole engineer across five surfaces
Period
2025 — present

Visit the live site

The problem

Premium car rental in Lebanon runs on phone calls, cash, and trust in whoever answered. There is no way for a renter to verify a host, no way for a host to hold a deposit without holding cash, and no record of anything when a trip goes wrong. Revv is a marketplace that puts the whole transaction, search, booking, deposit, inspection and dispute, inside one product.

Role and constraint

I built and operate every surface: the renter and host mobile app, the public website, the admin console, and the API behind them. The constraint that shaped the architecture was that the business wants to open in Montreal after Beirut, and I did not want to run two products to do it.

The decision: one deployment, market-scoped data

The obvious way to add a second country is a second deployment. Copy the stack, point it at a new database, change the currency. It is fast on day one and it is the option I rejected.

The cost shows up on day thirty. Every migration runs twice. Every bug fix ships twice. The admin team logs into two consoles. Analytics cannot answer a question across both. And the moment the two copies drift, which they will, you are maintaining two products with one team.

Instead the schema has an explicit market registry. Every business table carries a market reference. Lebanon and Montreal are rows in that table, each with its own currency, locale, payment methods and feature flags, and Montreal simply has booking switched off until launch. Every business route in the API is market-scoped through one middleware that resolves the market from a request header, so a client cannot reach data from the wrong market by accident.

The migration to this model was the hardest part. The system already had live data that assumed one country, so the change was staged: a compatibility flag defaults requests without a market header to Lebanon, which let the mobile clients update on their own schedule, and it will be turned off once every client sends the header. The migration that took ownership of the core tables uses an advisory lock with a five-second timeout so a deploy fails fast rather than hanging on a busy table.

What this defers is full isolation. Workers, storage and admin views are still shared across markets. That is a deliberate follow-on, not an oversight, and it is documented as such in the plan.

Staging is not a copy of production

The second decision worth defending is that staging is a different environment, not a smaller production. It has its own database, its own domains, its own Cloudflare project with no git integration, and a mock payment provider with test bypasses that production refuses to start with. The rule I work under is simple: never operate against production. Everything in this case study, including every screenshot, comes from staging.

That rule earned its keep the day I learned that merging documentation to the main branch deploys the admin console. Merging to main is a production action, and the backlog now says so in those words.

What it looks like

Revv home screen with search, pick-up and drop-off dates and a vehicle list
Search and dates. All listings are demo fleet vehicles on staging.
Vehicle detail screen for a Porsche 911 GT3 with features, host and price
Vehicle detail with a verified host and a locked chat until booking.
Booking confirmation sheet with subtotal, service fee, deposit and total
Checkout. The deposit is shown as a separate hold, not folded into the price.
The revvrental.com landing page with a coming soon headline over a night-time car photo
The public landing page. Its hero artwork is generated imagery; everything else on this page is real interface.
The Revv admin console sign-in screen
The admin console sign-in. The console itself handles vehicle review, bookings, deposits and disputes.

Evidence

Where it stands

The API, admin console and website are deployed. Booking launch is gated on the live card provider, and the deposit ledger is deliberately not marked launch-ready until provider references and reconciliation exist. I would rather say that here than have a recruiter find it in the app.

The source is private. I am happy to walk through any part of it.