48
API endpoints
56
Platform pages
876+
Source files
5
Health pillars
Foundations
Three principles. Before any code.
Before the first route was written, three ideas shaped every decision that followed. These aren't retroactive — they were the filters every decision went through during build.
01
Contract First
Design the API contract before building the UI. Define what the data looks like, what the endpoint accepts, and what it returns — then build the interface around that shape. This order eliminates most refactors before they happen.
02
Own the Runtime
No managed services with pricing changes. No SaaS dependencies that can go away. Everything runs on infrastructure I control: a VPS, nginx, PM2. The whole platform can be reproduced from a fresh server in under 30 minutes.
03
Simplicity Is a Feature
The simplest solution that meets the requirement is the right solution. Complexity has real carrying costs — debugging time, onboarding friction, failure surface. Every abstraction has to earn its place in the codebase.
Infrastructure
What those principles produced.
A self-hostable stack — Next.js frontend, Node/Express API hub, flat-file JSON persistence, nginx reverse proxy, PM2 process management. Designed to run on a $12/mo droplet and scale cleanly when it needs to.
Browser Next.js React · Tailwind Client reverse proxy nginx PM2 · SSL Gateway Express API API Hub 48 endpoints /api/users /api/workouts /api/nutrition /api/progress + 43 more Core flat-file JSON Store users · workouts · logs Persistence Cloudflare R2 Storage media · assets · exports Media
Reasoning
Key decisions, with the why.
Every architectural choice is a tradeoff. These are the ones that defined the shape of the system — what was considered, what was chosen, and what that choice actually costs.
01
How should the platform persist data?
Considered
PostgreSQL SQLite MongoDB Flat-file JSON
Chose
Flat-file JSON
Because
This is a single-tenant platform on one server. A relational database adds operational overhead — backups, migrations, connection pooling — without adding capability at this scale. JSON files are inspectable with any text editor, portable with cp, and zero-config to set up. When volume demands a migration, the API surface doesn't change.
02
Where does the platform run?
Considered
Vercel + managed DB Railway / Render AWS / GCP Bare VPS
Chose
DigitalOcean VPS + nginx
Because
PaaS platforms abstract away the exact things worth understanding — process management, reverse proxying, SSL termination, port binding. A bare VPS means I know why every piece is there. It's also $12/mo flat with no per-request billing surprises. The whole stack can be reproduced on a fresh server in under 30 minutes.
03
How should authentication work?
Considered
Auth0 / Clerk NextAuth Passport.js Custom JWT
Chose
Custom JWT middleware
Because
Third-party auth services are the right call for production multi-tenant apps. For a portfolio platform where understanding every layer matters, custom JWT middleware teaches what managed services hide. Token signing, expiry, refresh patterns, and role claims are all visible and auditable in under 80 lines.
04
How do five distinct pillars share one codebase?
Considered
Separate microservices Shared component lib Modular monolith
Chose
Modular monolith
Because
Microservices introduce network latency, deployment coordination, and service discovery overhead that this scale doesn't justify. A modular monolith gives each pillar — Move, Fuel, Recovery, Mind, Connect — its own routes, schemas, and UI components, while sharing auth middleware, DB helpers, and the design system without duplication.
Stack
Technology, chosen intentionally.
Nothing that requires a subscription to run, nothing that can't be self-hosted. Every tool was chosen because it was the most direct solution to a specific, defined problem.
Frontend
Next.js React Tailwind CSS TypeScript
Backend
Node.js Express JWT Auth PM2
Data
JSON Store Cloudflare R2
Infrastructure
nginx DigitalOcean Cloudflare CDN Let's Encrypt
Code
Principles in practice.
Four samples pulled from the running system — not written for the portfolio. Each one shows a principle at work: the API contract pattern, the flat-file data layer, the auditable auth middleware, and the adaptive dashboard hook.
API route — workout session JS
// Contract defined before the UI existed router.post('/sessions', authenticate, async (req, res) => { const { userId, exercises, notes } = req.body; const session = { id: nanoid(), userId, exercises, // [{ exerciseId, sets: [{reps,weight}] }] notes, createdAt: new Date().toISOString(), }; await db.append('sessions', session); res.status(201).json(session); });
Flat-file JSON data module JS
// Entire persistence layer — no ORM, no driver const db = { read(store) { const path = `./data/${store}.json`; if (!fs.existsSync(path)) return []; return JSON.parse(fs.readFileSync(path, 'utf8')); }, append(store, record) { const data = db.read(store); data.push(record); return db.write(store, data); }, write(store, data) { fs.writeFileSync( `./data/${store}.json`, JSON.stringify(data, null, 2) ); }, };
JWT auth middleware — auditable in 80 lines JS
function authenticate(req, res, next) { const header = req.headers.authorization; if (!header?.startsWith('Bearer ')) { return res .status(401) .json({ error: 'No token provided' }); } try { const token = header.slice(7); const payload = jwt.verify(token, process.env.JWT_SECRET); req.user = payload; next(); } catch { res.status(401).json({ error: 'Invalid token' }); } }
Adaptive dashboard — user context hook TSX
export function useDashboardContext() { const { user } = useAuth(); return useMemo(() => ({ layout: user.preferences.dashLayout ?? inferLayout(user.goal, user.experience), widgets: getWidgetsForGoal(user.goal), streak: calcStreak(user.sessions), nextWorkout: recommendNext( user.sessions, user.program ), }), [user]); }
Process
How I work.
The thinking that runs underneath the decisions — what guides it when the right answer isn't obvious.
How do you structure a new feature?

API route first, then schema, then UI. Designing the data contract before building the interface prevents most of the refactors that come later. The component trees almost always organize themselves once the data shape is clear. The opposite order — build UI first, figure out the data later — is where most complexity comes from.

How do you know when a solution is too complex?

When I can't explain it in a sentence. If an architecture requires a diagram to justify itself before it's even built, that's a signal. Complexity should arrive because the problem demands it — not because the solution was interesting to design. Three simple things are almost always better than one complicated one.

How do you use AI in your dev workflow?

For scaffolding, debugging unfamiliar errors, and pattern translation — "show me how this middleware pattern works in Express." The decisions about what to build, how it should behave, and how the pieces connect stay with me. AI accelerates execution. It doesn't replace the design thinking that happens before the first line.

What does "self-hostable" mean to you?

That it runs without third-party services that can change pricing, deprecate APIs, or disappear. The Train5D stack runs on a $12/mo DigitalOcean droplet with Cloudflare in front. No managed database, no SaaS auth provider. That's a constraint I chose on purpose — and it's made me a better developer for it.