Skip to content
Property2026

TafutaKeja

Property search by estate, with moderation that re-reviews a listing whenever it changes.

tafutakeja.netlify.app
Screenshot of TafutaKeja: Property, built with React, Express, MongoDB.

The problem

Property search fails on specificity. Kilimani and Kasarani are both Nairobi and nothing else about them is alike, so a county filter is close to useless — and the number a listing advertises is rarely the number anyone pays, once service charge and deposit are counted. Behind that sits a trust problem: anybody can list anything, and a marketplace that publishes whatever it is handed is worth less than one that does not.

Decisions

01

Editing a published listing sends it back for review

A moderator approved the words and photographs that were there at the time, not whatever replaces them. Without this rule approval is a one-time gate anyone can walk through: list something acceptable, wait for the tick, then swap the content. The transitions live in one exported state machine that the API enforces and the agent dashboard reads, so the UI renders exactly the buttons that will succeed instead of re-deriving the rules and drifting from them.

02

One visibility gate, composed by every read path

Drafts, listings in review and rejected listings are invisible to everyone except their own agent and a moderator. That is a single filter function every query composes, rather than a status check written out at each call site — because the failure mode of the second approach is one handler forgetting, and a draft quietly appearing in one grid.

03

A sold property keeps its page but stops advertising itself

The link is already out there and a dead URL is worse than an honest one, so the page stays — but it drops its structured data, asks search engines not to index it, and tells the reader it is gone. One predicate drives the robots tag and the structured data together, so the two cannot disagree. The deploy config deliberately puts no blanket rule over listing URLs, since that would stop a crawler ever reading the per-listing instruction.

04

Photographs are treated as the product

The catalogue ships 138 photographs, each rewritten into WebP at 400, 800 and 1600 pixels beside a resized fallback kept at its original filename. The database stores one path per photograph and the frontend derives the rest, so the catalogue is never one failed migration away from having no pictures. A browse page of twelve properties costs 204KB instead of several megabytes.

The hard part

Making a generated catalogue survive someone who knows the market

There is no open feed of Kenyan property, so the listings are composed — and a composed catalogue gives itself away on details a local reader checks without thinking. The first pass produced a one-bedroom maisonette, a two-bed in Kitengela at 137 square metres against a real 70 to 85, and an eighth-acre plot in Rongai at 300,000 shillings when the market is nearer two million. Each needed its own rule: bedroom counts constrained per property type, floor areas drawn per bedroom and scaled by how far up the market the area sits, and land priced per acre by belt rather than inferred from rent — Karen rents a fraction of what Kilimani does per bedroom while its land costs far more. Land and commercial units also needed their own prose, after the building copy produced "Recently painted plot" and closed a bare plot with a sentence about its rental history.