Case Study
Inside NearImpact: One Engineer, a Full Stack, and a $6 Server
How a final-year project became a live, self-hosted, two-sided platform — and what it taught its author about building and running software end to end.
Overview
NearImpact is a two-sided web platform that connects community organisations — NGOs, charities, universities and local groups — with the people who want to take part. Organisations publish structured opportunities; participants discover them by list or interactive map, apply, and track their application status. It was designed, built and is operated solo, and runs live in early access at nearimpact.site.
The Problem
Community opportunities are scattered across social feeds, mailing lists, bulletin boards and word of mouth. The people most in need of them — newcomers, those without established networks — are precisely the ones who never hear about them. Organisations, meanwhile, repeat the same manual outreach for every programme and struggle to see who actually engaged.
The Solution
One place to publish, discover and apply. Organisations manage a profile and post opportunities with structured fields; participants browse a searchable feed, explore a map of what is nearby, and apply through the platform with their status tracked from submission to decision.
A recommendation step surfaces relevant opportunities per participant, and full-text search makes the catalogue navigable as it grows. Everything sits behind a single sign-on so the same identity works across the app, its API and the admin tools.
Architecture
↓
↓ Bearer JWT
↓ ↓ ↓
All behind Caddy (auto-TLS) · 5 Docker containers · DigitalOcean · GitHub Actions CI
It is a three-tier system, end to end in TypeScript. The Next.js front end never touches the database directly; it calls a separate Express API with a bearer token, and the API speaks a consistent {data, error} contract. Authentication is delegated to a self-hosted Keycloak over OIDC; the API validates each token against Keycloak's JWKS and checks the caller's role before doing any work.
Engineering Challenges
One trustworthy authorisation boundary. Rather than spread access rules across the database with row-level security, the platform enforces them in one place — middleware that verifies the Keycloak JWT and the user's role. It is easier to audit and reason about, a decision I am happy to defend.
A language model that cannot run up a bill. The recommendation engine sends no personal data to the model, caches results for an hour, validates every suggestion against the real candidate set, and degrades gracefully when the API is unavailable — holding inference cost to roughly five cents a month.
Search that survives growth. Listings are indexed with PostgreSQL trigram (pg_trgm) full-text search, so the catalogue stays navigable as it fills, without bolting on a separate search service.
Operating it, not just shipping it. Five services had to start, talk to each other, obtain certificates and survive a redeploy — solved with Docker Compose, a Caddy reverse proxy that issues its own TLS, and continuous integration on every push.
Lessons Learned
Owning the operations is where the real learning lives — watching how the parts behave together in production taught me more than any single feature did.
A single, well-guarded boundary beats clever rules scattered everywhere; future me has to read this code.
Treat a language model like any external dependency: cap its cost, validate its output, and have a fallback. And write the deployment down — the runbook in the repo is as much a deliverable as the code.