The skills, with the evidence attached
The code behind all of it is public:
backend,
web,
mobile.
Backend engineering
Java 21
My strongest language. All 21 modules of RentManager's backend are modern Java: records for value types, sealed hierarchies where the domain has a closed set of cases, and the standard library where a framework isn't earning its place.
Spring Boot 3
Web, Data JPA, Security, Validation, Actuator and scheduled jobs, in a production deployment. I've tuned it to fit a 512 MB free-tier container without the scheduled payment sweeps failing to start.
Modular-monolith architecture
Ports and adapters with explicit module boundaries. One ArchUnit rule currently enforces that the property module's application layer never depends on its infrastructure, so CI catches that particular drift rather than me remembering. The other modules rely on convention so far, which is a gap I'd rather name than paper over.
Spring Security · JWT / JWKS
Clerk-issued tokens verified against a JWKS endpoint. The most important decision here was that the frontend sends a tenant id on every request and the backend ignores it completely, taking tenancy from the signed claim instead.
Multi-tenant data isolation
Enforced at two layers instead of one. In a multi-tenant system a single missed where clause is a data breach rather than a bug.
Concurrency & idempotency
A tenant tapping "pay" twice must not produce two charges. I've built idempotency windows, transactional boundaries around the ledger and a database-backed outbox. I also know which of those I'd want a second pair of eyes on.
Databases & data modelling
PostgreSQL
Schema design, constraints that make invalid states unrepresentable, indexing, and an append-only rent ledger. A row is written once, and a mistake is corrected with a new linked reversal instead of an edit.
Flyway migrations
99 sequential migrations, the latest at V104, none of them edited after running against a database. You can read the migration history back as the schema's real history.
JPA / Hibernate
Productive with entity mapping, fetch strategies and transaction boundaries. I'm still building judgement about when to stop fighting the ORM and just write the SQL.
Domain modelling
The part I enjoy most. Leases, occupancies, ledgers, commissions and payouts all carry real-world rules, and getting the model wrong costs far more than getting the code wrong.
Frontend
TypeScript
Client types are generated from the backend's own OpenAPI contract. Change a response shape in Java and the TypeScript build fails before anyone sees a broken screen.
React 19
258 components across four user roles, each seeing a different view of the same data.
Next.js (App Router)
62 routes, using server and client components, route handlers that proxy the backend, middleware-level auth, and a production build that's deployed.
TanStack Query
Query keys, invalidation after mutations, and optimistic updates only where rolling back is safe. On anything touching money, that's rarely.
Tailwind CSS · design systems
A consistent token-driven UI with light and dark themes. This portfolio is hand-written CSS with no framework behind it, which is its own kind of evidence.
Accessibility
Keyboard paths, focus states, semantic landmarks and honest alt text go in as I build rather than afterwards. I haven't yet tested a full product with real screen-reader users.
Mobile
React Native · Expo
A 44-screen RentManager app built on Expo Router, sharing the same generated API client as the web app. It runs, but I haven't published it to an app store yet, so that distribution step is still ahead of me.
Push notifications
Chosen over SMS for most alerts because SMS costs money per message and push doesn't. That decision came straight out of what the product could afford.
Offline & secure storage
Tokens live in the device keystore instead of plain storage, and connectivity detection means the app says it's offline instead of failing silently.
Payments & integrations
M-Pesa Daraja
Both directions: STK push to collect rent, B2C to pay landlords out. Callbacks are the hard part. They arrive late, twice, or out of order, and the ledger has to stay correct through all three.
Clerk authentication
JWKS verification, webhook handling for user lifecycle events, and role and organisation mapping shared by the web and mobile clients.
Africa's Talking SMS
Sent through a database-backed outbox with a scheduled sweep, so a provider outage delays a message instead of losing it.
Kenyan tax rules (VAT, MRI)
I read the Finance Act instead of a blog post, and built the module fail-closed, with every gate off until a human confirms it applies. I'm an engineer and not a tax adviser, and the software says as much.
Testing & quality
JUnit 5 · Mockito
1,588 tests across 261 test classes, written money-path first. Anything irreversible gets covered before the screen around it does.
Testcontainers
Integration tests run against a real PostgreSQL container. An in-memory database that doesn't enforce the same constraints will happily pass a test that production would reject.
ArchUnit
Architecture encoded as a test. A boundary that exists only in a diagram has usually been crossed already. Mine covers one module today, which makes it a start rather than a safety net.
Vitest · RN Testing Library
Component and hook coverage on the frontend. It's thinner than the backend suite, and I'd rather say so than imply they're equal.
Infrastructure & delivery
Docker
Multi-stage builds producing the image that runs in production, plus a Compose setup for local development against real Postgres.
Deploying a real system
RentManager is live, with the API on Render, the web app on Netlify and Postgres on Neon, plus a keep-awake ping and scheduled encrypted backups. I put that stack together under a hard constraint: it had to cost nothing. There's a runbook for it.
Git & CI
My daily driver, with GitHub Actions running the build and the nightly backup job.
Observability at production scale
Sentry, health checks and structured logs are in place. What I haven't done is run a system through real load and real incidents, and that's the gap I most want a team to help me close.
Distributed systems
Avoided on purpose. A modular monolith with a database outbox was the right call at this scale. Reaching for a message broker would have bought me a better architecture diagram and a worse system.