Web Applications
from schema to deploy
Responsive, fast web apps built with modern frameworks — server-rendered where it matters, interactive where it counts.
- Next.js / React
- Laravel / Livewire
- Design systems
Kigali, Rwanda
Software developer, since 2021
I design and ship production web and mobile applications — from database schema to deployed UI. I care about clean code, clear architecture, and software that holds up once real users arrive.
about me
I'm a full-stack developer based in Kigali. Since 2021 I've worked across incubators, an agritech company and a creative studio — shipping web apps, mobile apps and internal platforms that went on to carry real users and real data.
My range runs from database schema and API design through to the interface people actually touch. I've spent enough time on hosting, deployments and support tickets to build things that are calm to run, not just nice to demo.
I like problems where product thinking and engineering meet: the auction that needs to be provably fair, the registry that has to be trustworthy, the dashboard that turns a messy table into a decision.
working principles
Start with the data model
schema first, screens second
Make it work, then make it clear
readable beats clever
Tests are notes to future-me
especially the boring ones
Ship small, ship often
big-bang releases age badly
The boring solution usually wins
novelty is a cost
skills & stack
Grouped by where they sit in the system. Everything here has been used on something that shipped.
services
from schema to deploy
Responsive, fast web apps built with modern frameworks — server-rendered where it matters, interactive where it counts.
one codebase, two stores
Cross-platform mobile apps that feel native, share logic with the web, and ship to iOS and Android from a single React Native codebase.
the part users never see
REST and webhook APIs with sensible contracts, auth, rate limiting and docs — plus third-party integrations for payments, mail and auth.
decisions need numbers
Admin panels and analytics dashboards that turn raw tables into something a team can actually read and act on.
multi-tenant from day one
End-to-end SaaS builds: tenants, subscriptions, onboarding and billing — architected so adding the next customer is a config change, not a rewrite.
ship it, then sleep
CI/CD pipelines, server and domain setup, environment config and monitoring so releases are boring and rollbacks are one command.
featured work
A creative agency website showcasing innovative design and digital solutions for 63 Inc.
A comprehensive digital platform for managing creche operations, enrollment, and parent communication.
An all-in-one career platform connecting job seekers with opportunities.
The all-in-one platform for livestock farmers and consumers.
Problem
Livestock trade runs on word of mouth. Farmers and buyers had no shared, trustworthy place to list animals, agree prices, or keep records — so deals were slow, informal, and hard to verify.
My role
Full-stack developer / consultant. Built the marketplace and listing flows, authentication and roles, and the farmer + admin dashboards.
Technologies
Outcome
A single marketplace connecting farmers and consumers with structured listings and records.
A Rwanda-based digital security platform for bike ownership with smart security solutions.
Problem
Bike theft and ownership disputes were common because there was no digital registry linking a verified owner to a specific bike — recovery and resale checks relied on paper.
My role
Full-stack developer. Implemented the ownership registry, the verification workflow, and the admin tooling for handling claims.
Technologies
Outcome
A verifiable ownership record that makes a stolen bike much harder to resell.
selected code
A few small, real patterns — the kind of thing that ends up copied between projects.
import { useEffect, useRef, useState } from 'react'// Resolves true the first time the node enters the viewport, then unobserves.export function useInViewport<T extends Element>(margin = '-15%') { const ref = useRef<T>(null) const [seen, setSeen] = useState(false) useEffect(() => { const node = ref.current if (!node || seen) return const io = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setSeen(true) io.disconnect() } }, { rootMargin: margin }, ) io.observe(node) return () => io.disconnect() }, [seen, margin]) return [ref, seen] as const}import { NextResponse } from 'next/server'import { z } from 'zod'const Body = z.object({ email: z.string().email(), source: z.string().max(40).optional(),})export async function POST(req: Request) { const parsed = Body.safeParse(await req.json()) if (!parsed.success) { return NextResponse.json({ error: 'Invalid' }, { status: 422 }) } const { email, source } = parsed.data await db.subscriber.upsert({ where: { email }, update: { source }, create: { email, source }, }) return NextResponse.json({ ok: true }, { status: 201 })}npm run test -- --runnpm run buildphp artisan migrate --forcersync -az --delete ./build/ web@server:/srv/app/sudo systemctl reload app.servicecurl -sf https://example.com/health && echo "deploy ok"next page
Freelance projects, full-time roles, or a quick question about an idea — the inbox is open.