The thinking behind the build.
Full-stack development means more than writing code. It means deciding what to build, why that shape, and what you're explicitly choosing not to do. Here's how Train5D was architected — and the reasoning behind every layer.
// 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);
});
// 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)
);
},
};
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' });
}
}
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]);
}
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.
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.
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.
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.