Building Nerdy Nutrient: Self-Hosted Food Data App
#nutrition #self-hosting #open-data #nuxt #graphql #docker

Building Nerdy Nutrient: Self-Hosted Food Data App

Hamzahยท26 March 2026

Why Does This Exist?

I wanted to compare foods by their nutritional content - things like which cheeses have the highest fat, or how certain foods compare when you actually look at the numbers. This was mainly driven by curiosity.

Every nutrition site I tried gave me one food at a time. I wanted to line them up side by side, filter by a nutrient, and see the full picture at once. That tool using averaged UK data didn't exist, so I built it.

The data comes from the UK Government's COFID dataset rather than real supermarket products - ideally I would have used actual supermarket product data but that isn't freely accessible. For some reason that I can't understand, you have to pay a random third party company The downside is the values are averages rather than specific to a brand or product. That said, for comparing foods against each other it's still accurate enough to be useful.

The second motivation was more technical: I wanted to go end-to-end on a real deployment. Not just write code and push to Netlify/Vercel, but own the whole thing - server, infrastructure, monitoring etc. The kind of stuff that's easy to skip on side projects but you genuinely learn by doing.

So Nerdy Nutrient became both things at once: a food comparison tool backed by the UK Government's open nutritional dataset (COFID), and a full production stack I built and run myself.


The Data: UK Government COFID

The data powering the app comes from the Composition of Foods Integrated Dataset (COFID), published and maintained by the UK Government. It's the most comprehensive nutritional database for UK foods available - thousands of entries covering everything from raw ingredients to processed foods, with detailed breakdowns across macro and micronutrients.

The catch is that it comes as an Excel spreadsheet. A big one.

The first challenge was getting that data into something a web app could actually use. I wrote a parser to convert the spreadsheet into structured data, which then gets loaded into the database. The spreadsheet has its own internal conventions around naming and structure, so part of the work was normalising all of that into something consistent before it could be queried cleanly.


What the App Does

At its core, Nerdy Nutrient lets you search and filter foods from the COFID dataset, then compare them side by side. You can filter by specific nutrients using comparison thresholds - show me foods where protein is above X, or fat is below Y - and sort by any nutrient to quickly find the highest or lowest values. You can also save lists of foods to come back to, so if you're curious about a particular category you can build it out over time without starting from scratch each visit.


Privacy and Hosting

Privacy is a right, not a privilege. There's no reason a nutrition comparison tool should be tracking its users, so I made sure it isn't.

Analytics are handled by Umami, a privacy-friendly open source alternative to Google Analytics. It doesn't use cookies, doesn't track individuals across sessions, and sends nothing to third parties. I get basic aggregate data - page views, what's being used - without the surveillance side of it.

DNS and CDN runs through Cloudflare. Their privacy policy is notably better than most alternatives - they don't sell personal data. The DDoS protection, sign-up Captcha and performance benefits for a self-hosted app are also hard to replace without significant effort.

Error monitoring is handled by BugSink, a self-hosted error tracking tool. I strip out anything sensitive - auth tokens, emails, cookies - before it's captured, so error logs only ever contain what's actually useful for debugging.

The app is hosted in the EU on Hetzner, which keeps everything under GDPR jurisdiction and away from US data access laws.


What I Learned

The infrastructure side of this taught me more than the code side. I had built web apps before but always handed to deployment over to third parties. Going from zero to a fully self-hosted, production-grade setup - automated deployments, proper monitoring, SSL, server security updates, service health checks - was a different experience to writing application code.

Docker images in particular give you a lot of power. Your entire app is packaged, versioned, and reproducible - you can spin up the exact same stack on any machine in minutes. That kind of self-sufficiency is hard to appreciate until you have experienced the alternative of being tied to a specific platform or provider.


Technical Details

For anyone interested in what's running under the hood:

Frontend - Nuxt 4 (Vue 3, server-side rendered) with PrimeVue for UI components and Tailwind CSS for styling. State management via Pinia. The frontend runs as an SSR server in production rather than serving static files, which keeps the server-rendering working exactly as it does in development.

Backend - Fastify as the HTTP framework with GraphQL Yoga handling the API layer. GraphQL made sense here because the query patterns are flexible - filtering across 50+ nutrients with various operators would have been messy as REST endpoints. The schema for nutrients is auto-generated from the source data, so it always stays in sync. Account, login, authentication is handled via the REST endpoints.

Database - PostgreSQL, managed through Prisma ORM. Prisma's type safety and migration workflow made schema management straightforward.

Auth - Third party self-hosted authentication image, using passwordless magic link sign-in. No passwords to store or reset flows to maintain.

Infrastructure - Everything runs in Docker Compose. Caddy sits in front as a reverse proxy and handles HTTPS automatically. CI/CD runs on GitHub Actions, building and pushing Docker images to GitHub Container Registry on every merge to main. Playwright End-to-end tests run against a full production-equivalent stack on every PR before anything can merge.

Monitoring - BugSink for error capture (self-hosted, Sentry-compatible SDK), Umami for analytics.