A full-stack web app that helps families in Rwanda quickly find a loved one's grave. Instead of manually searching through an entire cemetery, users can search by name and instantly see the exact resting location removing the burden and emotional strain of not knowing where to look.
# Urwibutso - cemetery grave finder
Find a grave by **name**, or by **grave number + plot of land**, and get walking
directions on a map plus a **QR code** that reopens the grave on any phone.
Built for Rwandan cemeteries, where grave numbers restart at 1 in every plot -
so "grave 57" exists six times over, and families end up walking the whole
cemetery looking for the right one.
---
## The problem, and what this does about it
Headstones carry a number. That number is only unique **within its plot of
land**, but visitors are usually given the number alone. Six plots means six
graves numbered 57, and no way to tell which is theirs except walking to each.
| The situation | What Urwibutso does |
|---|---|
| A visitor knows the name only | Fuzzy name search - tolerates misspellings, accents, and either name order |
| A visitor knows the number only | Returns **every** plot holding that number and asks which one, instead of guessing |
| A visitor knows number **and** plot | Goes straight to one exact grave and its map pin |
| The visitor is standing in the cemetery | Live GPS dot, distance in metres, direction arrow, updating as they walk |
| They want to come back next year | A QR code per grave - scan it and the map reopens, no searching |
The ambiguity is enforced in the schema, not patched over in the UI: the natural
key on `graves` is `UNIQUE (plot_id, grave_number)`, never `grave_number` alone.
---
## Stack
| Layer | Technology |
|---|---|
| Frontend | React 18 + Vite, React Router, Leaflet / OpenStreetMap |
| Backend | Node.js 22, Express, `pg` |
| Database | PostgreSQL 16 with `pg_trgm` (fuzzy search) and `unaccent` |
| Load balancer | nginx - `least_conn` across two API instances, rate limiting, gzip |
| Packaging | Docker + Docker Compose |
```
┌──────────────────────────┐
phone / kiosk ──▶ │ nginx :80 (lb) │
│ rate limit, gzip, CSP │
└────┬────────────────┬────┘
/ │ (static) │ /api (least_conn)
┌────▼─────┐ ┌─────▼─────┐ ┌ …