Marketplace
Ridezio
Rent a bike or a car from a local shop, without ringing round
Shops list their vehicles, people book them for a set of dates, and both sides work from the same booking. I built it to launch as a real product after a trip where finding a bike meant calling shop after shop. It works end to end. It never found its users, so it sits here as the one that did not take off.
- Role
- Solo — full stack, API design, data modelling
- Timeline
- 2025
- Status
- Live
- Stack
- Next.js, TypeScript, Tailwind CSS, shadcn/ui, PostgreSQL, tRPC

01 — Context
The problem
Renting looks like a search problem and behaves like a calendar problem. Someone filters by type, price and place, but each result only means anything for a particular set of dates — and those dates are shared with everyone else looking at the same vehicle.
Shops need close to the opposite: one honest view of their vehicles, what is booked, and the gaps in between. The same records have to serve both sides without either one being shown something untrue.
02 — Decisions
How I approached it
A booking is a date range
Bookings are stored as ranges instead of two separate columns compared in my own code. Whether a vehicle is free on given dates becomes a question the database answers directly, rather than a loop over existing bookings.
Filters that stack into one query
Type, price, place and dates all become conditions in a single query. Adding a new filter means adding a condition, not another pass over results that have already been fetched.
A map beside the list
Listings carry coordinates, so 'near me' is an area on a map rather than a match on the spelling of a town — which matters where the same place is written three different ways.
One booking flow, two sides
Requested, confirmed, active, returned, cancelled. Shops and renters look at the same booking from different sides, instead of two separate flows that slowly drift apart.
03 — Structure
How it fits together
- Discovery
Filters · Map area as a filter · Availability check
One piece of state feeds both the map and the list.
- Booking
Booking states · Range overlap · Exclusion constraint
The database refuses a double booking, so the code does not have to be perfect.
- API
tRPC · Separate public and shop views
Shop numbers never travel out on the public endpoint.
- Data
PostgreSQL · daterange + GiST · Fleet tables
Date ranges are built in, so checking overlap is an index lookup.
04 — Problem solving
What was actually hard
Problem
Two people booking the same vehicle for overlapping dates in the same second would both pass a check written as 'fetch the bookings, then decide'. The checking and the saving were not one step, so both could win.
Solution
I moved the guarantee into the database with an exclusion constraint: it will not store two overlapping bookings for the same vehicle. The app still checks first so the message is friendly, but being right no longer depends on that check winning the race.
-- The database decides what overlaps, not the request handler.ALTER TABLE reservations ADD CONSTRAINT no_overlapping_bookings EXCLUDE USING gist ( vehicle_id WITH =, daterange(start_date, end_date, '[)') WITH && ) WHERE (status IN ('confirmed', 'active'));Problem
The map and the list disagreed. Moving the map fetched listings, changing a filter fetched them again, and the two answers arrived at different times — so the page flickered between them.
Solution
I folded both into one piece of state by treating the visible map area as just another filter. One fetch feeds the map and the list, so panning and filtering are the same operation underneath and there is nothing left to fall out of step.
05 — Result
Where it landed
- Double bookings
- Blocked by the database
- Search
- Knows the dates
- Discovery
- Map and list, one query
- Sides
- Shop and renter
What I'd do differently
I did not think cancellations through. Freeing up the dates is easy; deciding what happens to a rental that is half over, and who carries the cost, is a product question I answered in code before I answered it on paper. Next time I draw the whole booking flow, with the money written on each step, before the first migration.