Software · Health
NoBullFit
A privacy-first health and fitness tracker. I build the whole thing, frontend, backend, the data model, and the unglamorous infrastructure in between.
What it does
NoBullFit is a daily health log. You track food by meal, build and share recipes, record activities (with optional Strava sync), log your weight, keep grocery lists, and read it all back on a dashboard with charts and exportable PDF reports.
It runs a free tier and a Pro tier through Paddle. Pro adds forward planning. You can lay out meals and workouts ahead of time, track a weight goal with macro recommendations, and customize what your reports include.
Architecture
A single TypeScript codebase, frontend and backend together. React 19 with Vite on the client, server-rendered through React Router v7 loaders that run on the server first and then hydrate in the browser. Express 5 on the backend, PostgreSQL for relational data.
Food search is the interesting part. Instead of calling a third-party nutrition API on every keystroke, the app ships its own copy of the OpenFoodFacts dataset and queries it locally with DuckDB. Lookups never leave the server, which is both faster and a deliberate privacy choice.
Per-item data that does not fit a fixed schema (nutrients, activity fields, recipe steps) lives in JSONB columns, with composite indexes on the queries that actually run, which are almost always by user and by date.
Auditability
There is an append-only system log at the center of the app. Every meaningful action is categorized (account created, food logged, recipe published, billing portal opened) and every request records its method, endpoint, status, and duration. Sensitive query parameters are redacted before anything is written.
Integrations get their own trail. Each Strava import is recorded with timing, record counts, and retry state, so a sync that half-failed is visible rather than silent. Login attempts are logged with IP for brute-force detection.
Privacy by design
OAuth tokens for connected services are encrypted at rest with AES-256-GCM, each with its own IV. A token_version field on the user means a password change or logout invalidates every active session at once, with no revocation list to maintain.
There are no third-party trackers and no reCAPTCHA; signup uses a small self-hosted math check instead, so nothing about a new user is handed to an ad network. Data export is real and authenticated, a full JSON dump of everything tied to an account. Deletion is granular, scoped to a window or total, and cascades cleanly through the schema.
It is honest to call this privacy-conscious rather than maximal. Logs still carry email and IP for operability. The point is that the privacy claims map to mechanisms in the code, not to a paragraph in a policy.
- Self-hosted food database. OpenFoodFacts queried locally with DuckDB. No external food API calls.
- Append-only audit log. Every action categorized, every request logged and sanitized.
- Encrypted integration tokens. OAuth secrets stored with AES-256-GCM and per-record IVs.
- Instant session invalidation. A token_version field kills every session on password change or logout.
- Async webhook queue. Strava events queued and batch-processed with retries and deduplication.
- Real export and delete. Authenticated full-JSON export, time-scoped deletion, cascading through the schema.