Portfolio Logo

Gerald Bahati.

Full-Stack Software Engineer

contact

Based in Nairobi, Kenya

make contact

Accessibility

Mon – Fri: 9:00 - 18:00

Async communication via Email

Legal

Imprint
Data protection

©2026 Gerald Bahati | All rights reserved.

Previous ProjectWebline Store
Next ProjectModern Dashboard

Would you like to be featured here as well?

Feel free to use the contact form or other contact options.

Projekt anfragen

Therapy in Kenya

ProjectsTherapy in Kenya
Therapy in Kenya

Booking a first session should be the easy part

Overview

A booking and practice-management platform for a Nairobi counsellor. Someone looking for help is rarely in the mood to create an account — so the whole flow is built around booking as a guest in four short steps, then paying with the phone already in their hand.

Behind the public site sits a private dashboard for the practitioner and a Cloudflare Worker API shared by both.

Architecture

LayerTechnology
Public siteNext.js — booking, services, articles
DashboardNext.js — schedule, clients, payments
APItRPC on Cloudflare Workers
DatabaseD1 (SQLite) + Drizzle
RealtimeDurable Object per practice, WebSocket Hibernation
PaymentsM-Pesa Daraja (STK push)
MessagingAfrica's Talking SMS, Resend email

Booking without accounts

Choose a session type, pick a time, leave contact details, pay. Guests get a signed access link instead of a password, so they can manage the appointment later without ever registering.

Paid for by phone

M-Pesa STK push prompts the client's handset directly. Daraja callbacks arrive more than once and out of order, so payment state is keyed on the checkout request and applied idempotently — a duplicate callback is a no-op, not a double charge.

Reminders that don't double-send

A scheduled job sweeps upcoming appointments and sends SMS and email reminders. Overlapping runs are explicitly covered by tests, because a reminder job that fires twice is a message the client reads twice.

Customer:

Private counselling practice, Nairobi

Period:

2026

Performance:

Product & Booking Flow DesignFull-Stack EngineeringPayments Integration (M-Pesa)SMS & Email AutomationMulti-Tenant ArchitectureRealtime Systems

Features:

Four-step guest booking — no account requiredM-Pesa STK push with idempotent callbacksAutomated SMS and email remindersGoogle Calendar sync per practiceRealtime dashboard over WebSocketsMulti-tenant with enforced isolation

Deep Slate

Warm Sand

Accent Coral

Paper

Two people, one slot

Two guests can hit the same 10:00 slot in the same second. A "is this taken?" check before writing is a race, not a guard — it just narrows the window.

The application still does that check, because it produces a friendly message. Correctness comes from a partial unique index on organisation, date and time, scoped to active statuses only. The database refuses the second booking outright, and because the constraint ignores cancelled rows, a freed slot becomes bookable again with no cleanup job.

Payment callbacks you don't control

M-Pesa's Daraja API delivers callbacks more than once, sometimes out of order, occasionally long after the client has closed the tab.

Payment state is therefore keyed on the checkout request rather than assembled from the sequence of callbacks, and every transition is idempotent — replaying a callback changes nothing. Duplicate callbacks and out-of-order state changes are both covered by integration tests running against an isolated D1 instance.

A live dashboard without polling

The practitioner's dashboard should show a new booking the moment it lands, without hammering the database on an interval.

Each practice gets its own Durable Object acting as a notification hub. Producers send a lightweight poke, connected dashboards refetch authoritatively. It uses the WebSocket Hibernation API, so idle connections stay open without holding the object in memory — you pay for events, not for how long someone leaves the tab open.

One codebase, separate practices

The platform is multi-tenant, and in a mental-health context a leak between tenants is not a bug you get to explain away.

Every table carries an organisation, every query is scoped to it, and tenant isolation is asserted in the integration suite alongside the payment and booking cases rather than left to code review.

Full Therapy in Kenya homepage, header to footer
Homepage hero — 'A safe space to talk and heal'
Four-step booking wizard with session types and pricing
Counselling services page
Find help for — routing by concern
Articles and reflections listing
Tap to visit
Check it out!
Click to visit
Therapy in Kenya — counselling booking platform
From landing page to booked
Required to book or pay
Paid on the phone in hand