{
"title": "Task Management App Kenya: How SharkFlow's FlowTasks Works Under the Hood",
"content": "# Task Management App Kenya: How SharkFlow's FlowTasks Works Under the Hood\n\n## Why Task Management Matters in Kenya's Startup Ecosystem\n\nKenyans are building faster than ever. From Nairobi's Silicon Savannah to Kisumu's tech hubs, entrepreneurs juggle multiple projects, clients, and deadlines on devices that often have just 2GB of RAM and spotty 3G connections. The problem? Most productivity tools were designed for Silicon Valley bandwidth and MacBook Pro specs.\n\nSharkFlow's FlowTasks isn't another Asana clone. It's engineered from first principles for African entrepreneurs—especially those operating on 4G LTE that cuts out randomly, with payment workflows tied directly to M-Pesa, and the ability to work offline without losing critical data.\n\nLet's dive into how we built it.\n\n## The API Architecture: Lightweight and Resilient\n\nOur core philosophy: **minimize payload, maximize reliability**.\n\nWe designed FlowTasks around a GraphQL API (not REST) because it lets clients request *only* the fields they need. On a 2G fallback connection, this matters. A typical task object is ~50KB with REST over-fetching; with GraphQL, it's 8-12KB.\n\n```
graphql\nquery GetUserTasks($userId: ID!, $status: TaskStatus) {\n user(id: $userId) {\n tasks(status: $status) {\n id\n title\n dueDate\n assignee {\n name\n phone\n }\n subtasks {\n id\n completed\n }\n }\n }\n}\n
```\n\nThis single query replaces what would be 3-4 REST endpoints. For users on Safaricom's edge network in rural areas, every KB counts.\n\n### Mutation-First Design\n\nWe designed mutations to be **idempotent**—critical for spotty networks where users might tap \"Save\" multiple times thinking nothing happened.\n\n```
graphql\nmutation CreateTask(\n $title: String!\n $idempotencyKey: String!\n) {\n createTask(title: $title, idempotencyKey: $idempotencyKey) {\n id\n createdAt\n status\n }\n}\n
```\n\nEvery mutation includes an `idempotencyKey` (UUID generated client-side). Our backend deduplicates requests within a 24-hour window. Try to create the same task twice? You get the same response both times, with no duplicate in the database.\n\n## Database Architecture: PostgreSQL + SQLite Hybrid\n\n**Production server:** PostgreSQL (hosted on AWS RDS in `af-south-1` for latency to East Africa)\n\n**Client-side:** SQLite, synced via a custom offline-first engine\n\nWhy not just Firestore or MongoDB? Cost. Firestore costs scale badly in Africa where users create many small operations (tasks, comments, time entries). We built a time-series optimized schema instead:\n\n```
sql\n-- Tasks table (denormalized for reads)\nCREATE TABLE tasks (\n id UUID PRIMARY KEY,\n workspace_id UUID NOT NULL,\n creator_id UUID NOT NULL,\n title VARCHAR(500) NOT NULL,\n description TEXT,\n status VARCHAR(20) NOT NULL DEFAULT 'open',\n due_date TIMESTAMP,\n priority SMALLINT DEFAULT 3,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMP NOT NULL DEFAULT NOW(),\n deleted_at TIMESTAMP,\n FOREIGN KEY (workspace_id) REFERENCES workspaces(id),\n FOREIGN KEY (creator_id) REFERENCES users(id)\n);\n\n-- Event log (immutable, audit trail)\nCREATE TABLE task_events (\n id BIGSERIAL PRIMARY KEY,\n task_id UUID NOT NULL,\n event_type VARCHAR(50) NOT NULL,\n actor_id UUID NOT NULL,\n payload JSONB NOT NULL,\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\n FOREIGN KEY (task_id) REFERENCES tasks(id),\n FOREIGN KEY (actor_id) REFERENCES users(id)\n);\n\nCREATE INDEX idx_task_events_task_id ON task_events(task_id);\nCREATE INDEX idx_task_events_created_at ON task_events(created_at DESC);\n
```\n\nWe use an **event log** instead of updating rows. Why? It gives us:\n- Perfect audit trails (regulators in Kenya love this)\n- Easy conflict resolution for offline sync\n- Ability to replay state at any point in time\n\n## Offline-First Sync: The Real Magic\n\nThis is where SharkFlow differentiates from Notion, Monday.com, or Jira.\n\nUsers work entirely offline. FlowTasks syncs in the background when connectivity returns—intelligently batching requests and handling conflicts.\n\n```
typescript\n// Client-side sync engine (simplified)\nclass SyncEngine {\n private queue: PendingMutation[] = [];\n private lastSyncTimestamp: number = 0;\n\n async queueMutation(mutation: CreateTaskMutation): Promise<void> {\n // Immediately apply optimistically\n this.db.tasks.insert({\n id: uuid(),\n ...mutation,\n _syncStatus: 'pending',\n });\n\n // Add to queue\n this.queue.push({\n type: 'createTask',\n payload: mutation,\n clientId: this.clientId,\n timestamp: Date.now(),\n });\n\n // Trigger sync when online\n if (this.isOnline) this.sync();\n }\n\n async sync(): Promise<void> {\n if (this.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)