One marketplace, four kinds of user, one API
Yard.tt is a property marketplace for Trinidad & Tobago: a public site for finding a place, a portal for agents to list and manage inquiries, a flat-fee path for owners to list their own home, and an admin side to approve and moderate all of it. The website and the mobile app run on the same typed API.
Looking for a place in Trinidad & Tobago means checking a lot of different places, and listing one means posting it in all of them. I wanted a single place where the listings are structured — price, parish, bedrooms, a pin on a map — so a search can actually be a search.
A marketplace like this is really four products sharing a database. Visitors browse without an account. Registered users save homes and searches. Agents list, answer inquiries and pay to promote. Admins approve agents and moderate everything. Each needs a different view of the same data, and each is a place where one wrong check leaks someone else's.
Search by area and type with live counts, a map view, listing pages, favourites, and saved searches that email you when something new matches. The brand comes from a painted day-and-night view of a verandah house that switches with the theme.
Onboarding with admin approval, a listing editor, an inquiries inbox, paid promotions that lift a listing to the top of matching searches, and a Pro plan.
Someone letting or selling their own place can list it without an agent for a flat fee — TT$150 for 30 days to rent, TT$300 for 60 days to sell — and hear from people directly.
An Expo app in the same pnpm workspace. It imports the API's types but none of its code, and signs in with the same accounts through bearer tokens.
Every API call is built from one of a handful of procedure types — public, signed-in user, approved agent, admin — and the type is the check. Signed-in is never treated as authorised. A smoke test walks every admin and agent procedure as the wrong kind of user, so a new endpoint without the right guard fails the build instead of shipping.
The website and the app call the same tRPC router, and the app imports its types without importing any server code. A field renamed on the server is a type error in the app the same minute, rather than a crash report a week later.
Validation schemas, formatters and domain types live in a package both apps use. It is not allowed to depend on Next, React, the database or the auth provider — the moment it does, it stops being shareable, so the rule is written down and kept.
Contact forms and inquiries are callable only from server actions, which add rate limiting and a CAPTCHA before they reach the database. Nothing a stranger can submit is exposed as an endpoint they can script.
Promotions, Pro and owner listings all pay through one provider interface, so the payment company is a detail rather than a dependency. It already has two implementations behind it.
It isn't launched yet, so the honest outcome is about the build. Four roles on one API has held up: the access-control smoke test has been the difference between trusting a new endpoint and re-reading it line by line.
The shared package paid for itself the first time the mobile app needed a form. The validation, the price formatting and the feature labels were already there, and already the same as the website's.
Yard.tt is a marketplace, not a brokerage. It gives agents and owners a structured place to list and gives buyers and renters a real search. It doesn't handle the sale itself, and it isn't trying to.
If you are solving the same problem in your own stack, or you want this kind of foundation for your team, email me at hi@jaedan.me.