KiliMani Documentation
A full-stack Kenyan rental listing platform — renter app, landlord portal, and shared REST API.
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.
Data Flow
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.
API persists it
The Express server assigns a new ID, timestamps it, and writes it to the top of db.json.
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
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Expo / React Native | ~52.0 | Cross-platform mobile + web apps |
| Routing | Expo Router | ~4.0 | File-based tab navigation |
| Web rendering | React Native Web | ^0.19 | Run React Native in the browser |
| Icons | @expo/vector-icons (Ionicons) | ^14 | UI icons throughout both apps |
| Storage | AsyncStorage | ^1.23 | Local auth & saved listings |
| Maps | OpenStreetMap iframe | — | Embedded map (web only) |
| AI | Claude API (claude-sonnet-4-6) | — | AI rental assistant chat |
| API server | Express.js | ^4.18 | REST API bridging both apps |
| Database | JSON flat file (db.json) | — | Lightweight listing persistence |
| CORS | cors npm package | ^2.8 | Cross-origin requests |
| Language | TypeScript | ^5.3 | Type safety across both apps |
File Structure
Prerequisites
| Tool | Minimum Version | Install |
|---|---|---|
| Node.js | 18.x or later | https://nodejs.org |
| npm | 9.x or later | Bundled with Node.js |
| Expo CLI | Latest | npm 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.
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
| Service | URL | Default port |
|---|---|---|
| KiliMani API | http://localhost:3001 | 3001 |
| Rental App | http://localhost:8081 | 8081 |
| Landlord Portal | http://localhost:8082 | 8082 |
| API Documentation | http://localhost:3001/docs | 3001 |
Rental App
The primary renter-facing application built with Expo Router tabs.
Screens
| Screen | File | Description |
|---|---|---|
| Search (Home) | app/index.tsx | Hero, search bar, filter bar, listing cards, map, detail modal, auth modal |
| Map | app/map.tsx | Dedicated full-screen map tab |
| Saved | app/saved.tsx | Heart-saved listings persisted in AsyncStorage |
| AI Assistant | app/ai.tsx | Claude-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
| Screen | File | Description |
|---|---|---|
| Dashboard | app/index.tsx | Stats (total listings, views, enquiries, active listings) + quick actions |
| Add Property | app/add.tsx | 4-step wizard to publish a new listing |
| My Listings | app/listings.tsx | Search, toggle status (active/pending/inactive), delete listings |
| Enquiries | app/enquiries.tsx | Tenant enquiry inbox |
Add Property Wizard — Steps
Basic Info
Title, property type, monthly rent (KSh), size (sq ft), bedrooms, bathrooms, description.
Location
Address / neighbourhood (e.g. "Kilimani, Nairobi"). Map pin placeholder for future GPS integration.
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).
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 type | Maps to (API) |
|---|---|
| Apartment | apartment |
| House | house |
| Studio | studio |
| Maisonette | house |
| Townhouse | house |
| Bedsitter | studio |
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.
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
activelistings are returned byGET /listings - Persists across server restarts via
db.json - Documentation served at
/docs
API Endpoints
Base URL: http://localhost:3001
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"
},
...
]
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.
Updates the status of a listing. Valid values: "active", "inactive", "pending".
Request body
{ "status": "inactive" }
Response 200
Returns the updated listing object.
Permanently removes a listing from db.json.
Response 200
{ "ok": true }
Serves this HTML documentation page.
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
}
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_accountsAsyncStorage key - On sign-in, email and password matched against stored accounts
- Logged-in user stored in
auth_userAsyncStorage key - Avatar initials and user menu appear in the top nav when signed in
- Sign-out clears
auth_userfrom storage
Auth Modal Modes
| Mode | Fields | Trigger |
|---|---|---|
signin | Email, Password | "Sign in" button or sign-in gate |
register | Full 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
| Action | Gate heading | Post-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]));
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.
Configuration
| Setting | Value |
|---|---|
| Model | claude-sonnet-4-6 |
| Max tokens | 1000 |
| System prompt | Includes all current listings as JSON context |
| API endpoint | https://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?