{
"title": "Building ReserveFlow: AI-Powered Booking Infrastructure for Africa's Mobile-First Reality",
"content": "# Building ReserveFlow: AI-Powered Booking Infrastructure for Africa's Mobile-First Reality\n\nWhen SharkFlow started building ReserveFlow, our real estate and hotel booking AI, we faced a problem most Silicon Valley engineers never encounter: our users aren't on fiber. They're on 3G, dodgy 4G, or sometimes just EDGE networks. And they're booking properties while minibus-ing through Nairobi traffic.\n\nThis isn't a scaling problem that throwing more servers at solves. It's a fundamental architectural problem. Here's how we built it.\n\n## The Reality Check: Mobile-First Isn't Optional in Kenya\n\nLet's be clear: 60% of Kenya's 60M+ mobile money users don't have desktop computers. They're transacting on phones with 1-2GB RAM, unreliable connectivity, and data bundles that cost real money. ReserveFlow's API had to assume latency, packet loss, and intermittent connectivity as default states, not edge cases.\n\nMost SaaS platforms optimize for latency in milliseconds. We optimized for completing transactions in environments where you might lose connection mid-booking.\n\n## API Design: Designing for Offline-First Sync\n\nOur REST API is deliberately stateless and chunked. Instead of the typical monolithic booking flow, we broke it into atomic operations that can be queued, retried, and synced.\n\n```
typescript\n// Client-side queue manager\ninterface BookingOperation {\n id: string;\n type: 'SEARCH' | 'RESERVE' | 'PAY' | 'CONFIRM';\n timestamp: number;\n payload: any;\n retryCount: number;\n status: 'PENDING' | 'SYNCED' | 'FAILED';\n}\n\nclass BookingQueue {\n private queue: BookingOperation[] = [];\n private readonly MAX_RETRIES = 5;\n private readonly SYNC_INTERVAL = 15000; // 15 seconds\n\n async enqueue(operation: BookingOperation): Promise<void> {\n this.queue.push({\n ...operation,\n timestamp: Date.now(),\n retryCount: 0,\n status: 'PENDING'\n });\n await this.persistToLocalDB();\n }\n\n async syncWithServer(): Promise<void> {\n const pending = this.queue.filter(op => op.status === 'PENDING');\n \n for (const operation of pending) {\n try {\n const response = await fetch(\n `${API_BASE}/bookings/${operation.type.toLowerCase()}`,\n {\n method: 'POST',\n body: JSON.stringify(operation.payload),\n headers: { 'X-Idempotency-Key': operation.id }\n }\n );\n \n if (response.ok) {\n operation.status = 'SYNCED';\n } else if (response.status === 429 || response.status >= 500) {\n // Server error or rate limited - retry later\n operation.retryCount++;\n if (operation.retryCount >= this.MAX_RETRIES) {\n operation.status = 'FAILED';\n }\n }\n } catch (error) {\n // Network error - will retry on next sync\n operation.retryCount++;\n }\n }\n \n await this.persistToLocalDB();\n }\n\n private async persistToLocalDB(): Promise<void> {\n // SQLite on mobile, IndexedDB on web\n await localDB.saveOperations(this.queue);\n }\n}\n
```\n\nThe key insight: idempotency is non-negotiable. Every operation gets a unique ID, and the server never processes the same booking twice, even if the client retries five times.\n\n## Backend API Endpoints: Minimal Data Transfer\n\nOur endpoints are designed to minimize payload size. We use compression, pagination, and field filtering.\n\n```
javascript\n// Example: Search properties with minimal payload\nGET /api/v1/properties/search?limit=10&offset=0&fields=id,name,price,rating&location=nairobi&budget=5000-15000\n\nResponse (gzipped, ~4KB):\n{\n \"properties\": [\n {\n \"id\": \"prop_abc123\",\n \"name\": \"Riverside Apartments\",\n \"price\": 8500,\n \"rating\": 4.7,\n \"location\": \"Westlands\"\n }\n ],\n \"next\": \"/api/v1/properties/search?offset=10&...\",\n \"total\": 234\n}\n
```\n\nCompare that to the typical SPA approach where you'd send the entire property object with 50+ fields, nested user reviews, amenities arrays, etc. Our responses are 70-80% smaller.\n\n## Database Architecture: PostgreSQL + Redis + S3\n\nWe use PostgreSQL for transactional data because real estate bookings aren't eventual-consistent problems—double-booking costs money. But we needed to make it fast on slow networks.\n\n```
sql\n-- Core booking table with denormalized fields for quick queries\nCREATE TABLE bookings (\n id UUID PRIMARY KEY,\n user_id UUID NOT NULL,\n property_id UUID NOT NULL,\n check_in DATE NOT NULL,\n check_out DATE NOT NULL,\n status ENUM ('RESERVED', 'PAID', 'CONFIRMED', 'CANCELLED') NOT NULL,\n total_price DECIMAL(10,2) NOT NULL,\n payment_method VARCHAR(50),\n m_pesa_tx_id VARCHAR(255) UNIQUE,\n created_at
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)