NutriKor
A nutrition diary that lives in Telegram. Send the bot a photo, a voice note or a line of text; it becomes a meal you confirm, then a day, a week and a trend.


189
n8n workflow nodes replaced by one service
3
ways to log a meal: photo, voice, text
6
interface languages
1,100+
automated tests
- Client
- KorLab, our own product
- Platform
- Telegram bot and Mini App, light and dark
- Languages
- English, German, Polish, Ukrainian, Belarusian, Russian
- Our part
- Product, design, agent, backend, Mini App
- Timeline
- n8n prototype, rebuilt on NestJS in 2026
The brief.
Context
NutriKor is a nutrition diary for people who train for a result, whether that means bulking, cutting or holding weight. Food goes in through the Telegram chat. The Mini App, opened from the same chat, turns those entries into a readable day, weekly reports and targets that change by period. It is our own product, from the brand to the deploy pipeline.
Challenge
The first version was a 189-node n8n workflow. A meal waiting for confirmation lived only in the agent’s eight-turn conversation buffer, so it vanished after a few messages or a restart. The Confirm button carried the bare word “confirm” with no draft id, so tapping an older message could save whichever meal was still in the window.
Approach
We rebuilt it as a NestJS service on App Engine. A draft is now a Firestore document with a stable id, and that id travels inside the button. Nutrition comes from USDA FoodData Central, then Open Food Facts, then an explicit not found, so the agent never makes up a calorie count. The Mini App reads the same data through a small REST API.
Photo, voice or text in. A draft out.
There is no form to fill in. You send the bot a photo of the plate, a voice note or a line like “salmon, potatoes, salad”. The agent splits the meal into parts, estimates each portion and looks up the figures per 100 g. The result waits as a draft until you confirm it, correct the grams or delete it.
- Foods you log go into a personal product book, marked as verified, a reference or your own figures.



From one day to a week you can read.
Today shows calories and macros against the target and what is left. Reports average the last 7 or 30 days. Insights compare the week with the one before and end on one suggested next step.
- A new target opens a new period from the day you choose. Past days stay measured against the target that was in force then.



Friends, green days and levels.
Friends share a weekly board with a score out of 100 for each day. Seeing someone’s diary is a separate request they approve. Each of the four targets met at the end of a day is a green day and earns 1 XP, so a day is worth up to 4. Levels follow from XP.


A button that confirms the right meal.
The n8n version broke on a Confirm button that did not know which meal it belonged to. Now the draft id is part of the button’s payload. A tap three days later finds exactly that draft and files it on its own date. The saved meal takes the draft’s id, so a second tap changes nothing.
- Day totals are updated in the same Firestore transaction as the meal.
Telegram
A photo, voice note or text reaches the webhook. Voice notes are transcribed first.
Agent
A tool-calling loop of at most eight rounds splits the meal and asks the nutrition tools for figures.
Nutrition lookup
USDA FoodData Central, then Open Food Facts, then an explicit not found. No guessed numbers.
Draft
Stored in Firestore with a stable id. The bot replies with buttons that carry that id.
Diary
Confirm saves the meal under the draft’s id and updates the day totals in one transaction.
Everything around the diary.
Around the diary sit the parts that keep it running: messages the bot sends on its own, a console that shows what the model costs, and a deploy that can undo itself.
Morning digest
Every day at 06:00 UTC the bot sends a summary of the day before, with nudges for quiet users.
Spend ledger
Every model and transcription call is recorded and reconciled daily against the OpenAI bill in a FireCMS console.
Canary deploys
Lint, unit and end-to-end tests, a no-traffic App Engine version, a health check, then a traffic shift that rolls back on failure.
No keys in the repo
Secrets load from Secret Manager at boot, and CI deploys through Workload Identity Federation.
One build, three hosts
The same web build runs inside Telegram, in a browser and in a Flutter shell for iOS and Android.
Telegram Stars
Voluntary support through Telegram Stars, with invoices, a ledger and refunds.
Calm, numeric, at home in Telegram.
The Mini App reads Telegram’s own theme, so it opens light or dark to match the chat it came from. Colour is kept for meaning: one per macro, green for a target met, amber when a day drifts. Numbers are large; everything else stays quiet.
- Protein
- Fat
- Carbohydrates
- Calories
- On target
- Drifting
- Paper


What we built.
Our part
- Product idea, brand and interface design
- LLM agent and its nutrition tools
- NestJS API with persistent drafts and day totals
- Telegram Mini App in Vite and TypeScript
- Flutter shell for iOS and Android
- FireCMS admin with model spend tracking
- CI/CD with canary rollout and automatic rollback
Stack
- Mini App
- Vite
- TypeScript
- Telegram Web Apps SDK
- Flutter shell
- Backend
- NestJS
- Firestore
- Google App Engine
- Firebase Auth
- zod
- AI and data
- OpenAI tool calling
- OpenAI transcription
- USDA FoodData Central
- Open Food Facts
- Operations
- GitHub Actions
- Secret Manager
- App Engine cron
- FireCMS
- Telegram Stars
Outgrowing your n8n workflow?
Let’s make it a product.
We rebuild no-code prototypes as services with real data models, tests and deploys. Tell us what yours does.



