Vurel
Market intelligence for Thai real estate · vurel.io
Vurel is my own product. Scrapers pull property listings from nine Thai portals every night, Postgres turns them into pricing signals, and agents read the result on a multitenant dashboard. I designed, built, and operate every layer: schema, security model, billing, servers.
Client work shows what I deliver inside someone else's product. Vurel shows what I build when every decision is mine. That is why it gets a page.
1M+ listings processed9 portalsnightly ingestsolo, end to end
The pipeline
9 portals→nightly scrapers→Postgres→signals→dashboard
A dedicated worker box runs the scraper fleet on a nightly schedule. Each portal gets its own scraper with shared infrastructure underneath: connection pooling, exponential backoff, rate limiting, and a zone taxonomy that maps every listing to a consistent geography regardless of how the source site describes it.
Listings upsert against stable portal identifiers, so a repeat scrape updates price and status instead of duplicating rows. Price changes append to a history table, which is what makes the interesting signals possible: price cuts, days on market, underpriced outliers against zone medians. Contact details are enriched where portals expose them and masked in the product until a workspace is on a paid plan.
Over a million listings have moved through this pipeline. The hard part was never the first scrape. It was building something that still produces a trustworthy dataset after months of portals changing markup, throttling traffic, and going down in the middle of a run.
The data model
The dashboard is multitenant: workspaces, members, and access scoped by zone, enforced with row level security in Postgres rather than in application code. A client subscribed to certain zones sees those zones and nothing else, and that guarantee holds even if a query path is added later by someone who never read the auth code.
The hot paths that needed SECURITY DEFINER functions get their tenant scoping inside the function itself, checked against the caller's active subscription, because a definer function that trusts its caller is just an RLS bypass with extra steps. Phone numbers mask at the view layer for unpaid users. Access follows billing state: Stripe subscriptions gate what a workspace can see, with trials handled server side.
This is the same class of work clients hire me for. The difference is that here I am also the one who gets paged when it is wrong.
Running it
Everything runs on infrastructure I manage: self hosted Supabase and both apps deployed through Coolify on a VPS, with self hosted analytics beside them. The dashboard is React with Vite; the marketing site is Next.js.
At a million rows, dashboard queries stopped being free. Nightly pg_cron jobs rebuild denormalized cache tables and daily analytics snapshots, so the pages agents actually load stay fast without every request paying for aggregation. The same scheduler runs saved search alerts and keeps the contact directory current.
The point
If you hire me for your backend, this is the standard your product gets: tenancy enforced in the database, billing wired to access, pipelines that survive bad nights, and operations documented well enough that the next developer can run them. Vurel is where that standard gets tested on me first.
Room for one more project right now