KiliMani Documentation

A full-stack Kenyan rental listing platform — renter app, landlord portal, and shared REST API.

Expo 52 React Native Web Node.js API Claude AI Nairobi, Kenya
⬇ Download Documentation (HTML)

Introduction

KiliMani is a rental property listing platform built for the Kenyan market, focusing on Nairobi. It consists of three interconnected applications that together provide a complete end-to-end solution for renters, landlords, and administrators.

🏠

Rental App

Renter-facing app with search, filters, map view, saved listings, and AI-powered assistant.

🏢

Landlord Portal

4-step property listing wizard, dashboard with stats, listings manager, and enquiries inbox.

âš¡

KiliMani API

Lightweight Express REST API that bridges both apps, persisting listings in a JSON database.

Architecture

The three services communicate over localhost during development. The rental app always reads listings from the API and falls back to static data if the API is unreachable.

┌─────────────────────────────────────────────────────────────────┐ │ KiliMani Platform │ ├────────────────┬──────────────────────┬──────────────────────────┤ │ │ │ │ │ Rental App │ Landlord Portal │ KiliMani API │ │ :8081 │ :8082 │ :3001 │ │ │ │ │ │ GET /listings ──────────────────────────────────────► │ │ │ POST /listings ──────────────────────────────► │ │ │ │ Reads/writes db.json │ │ │ │ │ └────────────────┴──────────────────────┴──────────────────────────┘ Renter clicks Landlord fills Express + JSON flat DB Search / Map / 4-step wizard & CORS-enabled, stateless AI / Saved tabs publishes listing

Data Flow

1

Landlord publishes a property

Fills the 4-step form in the landlord portal and clicks "Publish listing". The portal POSTs the listing data to POST /listings.

2

API persists it

The Express server assigns a new ID, timestamps it, and writes it to the top of db.json.

3

Rental app picks it up

Every time the Search tab comes into focus, the rental app calls GET /listings and re-renders the list — new listings appear at the top automatically.

Tech Stack

LayerTechnologyVersionPurpose
FrameworkExpo / React Native~52.0Cross-platform mobile + web apps
RoutingExpo Router~4.0File-based tab navigation
Web renderingReact Native Web^0.19Run React Native in the browser
Icons@expo/vector-icons (Ionicons)^14UI icons throughout both apps
StorageAsyncStorage^1.23Local auth & saved listings
MapsOpenStreetMap iframe—Embedded map (web only)
AIClaude API (claude-sonnet-4-6)—AI rental assistant chat
API serverExpress.js^4.18REST API bridging both apps
DatabaseJSON flat file (db.json)—Lightweight listing persistence
CORScors npm package^2.8Cross-origin requests
LanguageTypeScript^5.3Type safety across both apps

File Structure

KiliMani/source/ │ ├── rental-app/ # Renter-facing Expo app (port 8081) │ ├── app/ │ │ ├── _layout.tsx # Tab navigator (Search/Map/Saved/AI) │ │ ├── index.tsx # Homepage — search, filters, listings │ │ ├── map.tsx # Dedicated map tab │ │ ├── saved.tsx # Saved/hearted listings │ │ └── ai.tsx # AI assistant chat screen │ └── src/ │ ├── theme.ts # Colors, Spacing, Radius constants │ └── data/ │ └── listings.ts # Static fallback listings (10 properties) │ ├── landlord-portal/ # Landlord-facing Expo app (port 8082) │ └── app/ │ ├── _layout.tsx # Tab navigator │ ├── index.tsx # Dashboard (stats, quick actions) │ ├── add.tsx # 4-step Add Property wizard │ ├── listings.tsx # My Listings manager │ └── enquiries.tsx # Tenant enquiries inbox │ └── api/ # Shared REST API (port 3001) ├── server.js # Express server with 4 endpoints ├── db.json # JSON flat-file database ├── package.json └── docs/ └── index.html # This documentation

Prerequisites

Ensure the following are installed before proceeding.
ToolMinimum VersionInstall
Node.js18.x or laterhttps://nodejs.org
npm9.x or laterBundled with Node.js
Expo CLILatestnpm install -g expo-cli

Installation

Clone or download the project, then install dependencies for each sub-application.

1. Install Rental App dependencies

cd C:\Projects\KiliMani\source\rental-app
npm install

2. Install Landlord Portal dependencies

cd C:\Projects\KiliMani\source\landlord-portal
npm install

3. Install API dependencies

cd C:\Projects\KiliMani\source\api
npm install

Running the Apps

Open three separate terminal windows and run one command in each.

Always start the API server first (Terminal 1) before launching either app. The rental app fetches listings from the API on startup.

Terminal 1 — API Server

cd C:\Projects\KiliMani\source\api
node server.js
# → KiliMani API running on http://localhost:3001

Terminal 2 — Rental App

cd C:\Projects\KiliMani\source\rental-app
npx expo start --web --port 8081
# → Open http://localhost:8081 in your browser

Terminal 3 — Landlord Portal

cd C:\Projects\KiliMani\source\landlord-portal
npx expo start --web --port 8082
# → Open http://localhost:8082 in your browser

Service Summary

ServiceURLDefault port
KiliMani APIhttp://localhost:30013001
Rental Apphttp://localhost:80818081
Landlord Portalhttp://localhost:80828082
API Documentationhttp://localhost:3001/docs3001

Rental App

The primary renter-facing application built with Expo Router tabs.

Screens

ScreenFileDescription
Search (Home)app/index.tsxHero, search bar, filter bar, listing cards, map, detail modal, auth modal
Mapapp/map.tsxDedicated full-screen map tab
Savedapp/saved.tsxHeart-saved listings persisted in AsyncStorage
AI Assistantapp/ai.tsxClaude-powered chat for rental recommendations

View Modes

  • List view — vertical scrollable list of property cards with independent scrollbar
  • Map view — OpenStreetMap embedded iframe with price pins
  • Split view — list on the left, map on the right (default on web)

Filter Bar

  • Property type chips: All, Apartment, House, Studio
  • Any beds dropdown (Any / 1+ / 2+ / 3+)
  • KSh price dropdown (Any / Under 50K / 100K / 200K / 400K)
  • Pets filter (Any / Pets OK)
  • Reset filters button (appears when any filter is active)

Default View by Platform

// Web users land on Split view; mobile users land on List view
const [viewMode, setViewMode] = useState(
  Platform.OS === 'web' ? 'split' : 'list'
);

Landlord Portal

The landlord-facing application at http://localhost:8082, providing property management tools.

Screens

ScreenFileDescription
Dashboardapp/index.tsxStats (total listings, views, enquiries, active listings) + quick actions
Add Propertyapp/add.tsx4-step wizard to publish a new listing
My Listingsapp/listings.tsxSearch, toggle status (active/pending/inactive), delete listings
Enquiriesapp/enquiries.tsxTenant enquiry inbox

Add Property Wizard — Steps

1

Basic Info

Title, property type, monthly rent (KSh), size (sq ft), bedrooms, bathrooms, description.

2

Location

Address / neighbourhood (e.g. "Kilimani, Nairobi"). Map pin placeholder for future GPS integration.

3

Photos & Features

Photo upload zone (up to 10 photos), amenity toggles (Wi-Fi, Parking, Pool, etc.), and property feature switches (Pets, Furnished, Water included, DSQ).

4

Contact & Publish

Landlord name, phone, email, deposit amount. Clicking "Publish listing" POSTs to the API and the listing goes live immediately.

Property Type Mapping

The portal supports more property types than the rental app filter. These are mapped on submission:

Portal typeMaps to (API)
Apartmentapartment
Househouse
Studiostudio
Maisonettehouse
Townhousehouse
Bedsitterstudio

API Server

A lightweight Express.js REST API that acts as the data bridge between both apps. Listings are stored in db.json — a plain JSON array on disk.

The API is intentionally simple. For production, replace db.json with a real database (PostgreSQL, MongoDB, Firebase Firestore, etc.) and add authentication middleware.

Key characteristics

  • CORS enabled for all origins (both apps can call it)
  • New listings are prepended (most recent first)
  • Only active listings are returned by GET /listings
  • Persists across server restarts via db.json
  • Documentation served at /docs

API Endpoints

Base URL: http://localhost:3001

GET /listings

Returns all active listings from db.json. Used by the rental app on every Search tab focus.

Response 200

[
  {
    "id": 11,
    "title": "Luxury 2BR Apartment",
    "type": "apartment",
    "location": "Parklands, Nairobi",
    "price": 175000,
    "beds": 2,
    "baths": 2,
    "sqft": 950,
    "pets": false,
    "lat": -1.259,
    "lng": 36.821,
    "ai": false,
    "status": "active",
    "desc": "Brand-new 2-bedroom apartment...",
    "images": ["https://..."],
    "amenities": ["Wi-Fi", "Parking", "Gym"],
    "landlord": { "name": "...", "phone": "...", "email": "..." },
    "createdAt": "2026-06-28T22:56:16.279Z"
  },
  ...
]
POST /listings

Creates a new listing. Called by the landlord portal on form submission. The server auto-assigns id, status: "active", ai: false, and createdAt.

Request body

{
  "title":     "Luxury 2BR Apartment",
  "type":      "apartment",
  "location":  "Parklands, Nairobi",
  "price":     175000,
  "beds":      2,
  "baths":     2,
  "sqft":      950,
  "pets":      false,
  "lat":       -1.259,
  "lng":       36.821,
  "desc":      "Description text...",
  "images":    ["https://..."],
  "amenities": ["Wi-Fi", "Parking"],
  "landlord":  { "name": "...", "phone": "...", "email": "..." }
}

Response 201

Returns the created listing object with id and createdAt populated.

PATCH /listings/:id/status

Updates the status of a listing. Valid values: "active", "inactive", "pending".

Request body

{ "status": "inactive" }

Response 200

Returns the updated listing object.

DELETE /listings/:id

Permanently removes a listing from db.json.

Response 200

{ "ok": true }
GET /docs

Serves this HTML documentation page.

GET /docs/download

Downloads this HTML documentation as a file (KiliMani-docs.html).

Data Models

Listing

interface Listing {
  id:        number;
  title:     string;
  type:      'apartment' | 'house' | 'studio';
  location:  string;
  price:     number;           // Monthly rent in KSh
  beds:      number;
  baths:     number;
  sqft:      number;
  pets:      boolean;
  lat:       number;           // Latitude (Nairobi area)
  lng:       number;           // Longitude
  ai:        boolean;          // true = AI Pick badge
  status:    'active' | 'inactive' | 'pending';
  desc:      string;
  images:    string[];         // Array of image URLs
  amenities: string[];
  landlord:  {
    name:  string;
    phone: string;
    email: string;
  };
  createdAt?: string;          // ISO timestamp (API-added listings)
}

User (AsyncStorage — Rental App)

interface User {
  name:  string;
  email: string;
}

interface Account {
  name:     string;
  email:    string;
  password: string;   // Stored locally — not sent to any server
}
Security note: User accounts are currently stored in AsyncStorage (local device storage only). Passwords are in plaintext. For production, implement a proper backend authentication service with hashed passwords.

Authentication

The rental app uses a fully local authentication system backed by AsyncStorage. No network calls are made for auth — accounts are stored on the user's device.

Auth Flow

  • User registers with name, email, and password (min 6 characters)
  • Account stored in auth_accounts AsyncStorage key
  • On sign-in, email and password matched against stored accounts
  • Logged-in user stored in auth_user AsyncStorage key
  • Avatar initials and user menu appear in the top nav when signed in
  • Sign-out clears auth_user from storage

Auth Modal Modes

ModeFieldsTrigger
signinEmail, Password"Sign in" button or sign-in gate
registerFull name, Email, Password"Create one" link

CAPTCHA — Human Verification

A lightweight built-in CAPTCHA is shown inside the auth modal before the user can submit, preventing automated sign-ups without any external service or API key.

How it works

  • Two random integers (1–9) are generated when the auth modal opens
  • The user must correctly answer the addition: "What is A + B?"
  • Correct answer → green "Verified — you're human!" confirmation
  • Wrong answer → error shown, new question generated automatically
  • Submit button is blocked until CAPTCHA is verified
  • New question generated on every modal open
const generateCaptcha = () => {
  setCaptchaA(Math.floor(Math.random() * 9) + 1);
  setCaptchaB(Math.floor(Math.random() * 9) + 1);
  setCaptchaInput('');
  setCaptchaVerified(false);
};

const verifyCaptcha = () => {
  if (parseInt(captchaInput, 10) === captchaA + captchaB) {
    setCaptchaVerified(true);
  } else {
    setCaptchaError(true);
    generateCaptcha(); // new question on wrong answer
  }
};

Sign-in Gate

Unauthenticated users are shown a sign-in gate modal when they try to access protected actions, instead of being silently blocked.

Gated actions

ActionGate headingPost-auth behaviour
Click a property card"Sign in to view details"Listing detail modal opens automatically
Click "+ List property""Sign in to list your property"Landlord portal opens automatically

Gate modal options

  • Continue with Google — placeholder (requires Google OAuth Client ID to activate)
  • Continue with email — opens email sign-in form
  • Create one — opens registration form
  • After successful auth, the original action is completed automatically (no need to click again)

Listings Sync

The rental app fetches live listings from the API every time the Search tab is focused, ensuring newly published properties appear without a page refresh.

const fetchListings = useCallback(async () => {
  setListingsLoading(true);
  try {
    const res = await fetch('http://localhost:3001/listings');
    if (res.ok) {
      const data = await res.json();
      setListings(data.length > 0 ? data : LISTINGS); // fallback
    }
  } catch {
    // API offline — static LISTINGS remain displayed
  } finally {
    setListingsLoading(false);
  }
}, []);

useFocusEffect(useCallback(() => {
  fetchListings();
}, [fetchListings]));
A small spinner appears next to "Showing X properties" while listings are loading from the API.

AI Assistant

The AI tab provides a conversational rental assistant powered by Claude (claude-sonnet-4-6). It knows all current listings and helps renters find the best match.

Important: The current implementation calls the Anthropic API directly from the client. This exposes your API key if inspected in the browser. For production, proxy requests through a backend server and store the key server-side.

Configuration

SettingValue
Modelclaude-sonnet-4-6
Max tokens1000
System promptIncludes all current listings as JSON context
API endpointhttps://api.anthropic.com/v1/messages

Suggested questions

  • Find me a 2-bed apartment under KSh 150,000
  • Show pet-friendly houses in Karen
  • What is the cheapest listing available?
  • Best value for a family with 3 kids?