Skip to content

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
github.com/ujen5173/Ridezio
Ridezio screenshot

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

01

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.

02

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.

03

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.

04

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

01

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.

sql
-- 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'));
02

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.