Back

Macronutri: from a no-code prototype to production, with security in the database

A SaaS for Brazilian dietitians. I took it from a no-code prototype to a production product in about three weeks, with the business rules and tenant isolation enforced in Postgres.

Role
Co-founder & CTO
Context
Portuguese-language SaaS for independent dietitians and small clinics
Period
Oct 13 – Nov 1, 2026
Status
In production since 2026-11-01
Links
  • React 19
  • TypeScript
  • Vite
  • Tailwind CSS 4
  • TanStack Query
  • Supabase
  • PostgreSQL 17
  • Edge Functions (Deno 2)
  • pg_cron
  • Cloudflare Workers
  • GitHub Actions
  • Playwright
  • Vitest

Problem

Macronutri began as a prototype on Base44, a no-code platform. It validated the idea. It was not a product the business could own or scale: no control over the code, the data or the roadmap.

Three problems had to be solved before real dietitians could rely on it:

  • Rules ran only in the browser. The free-plan patient limit and the subscription tier could be bypassed.
  • It handles health data. Health data is sensitive under LGPD (art. 11), Brazil’s data protection law. Tenant isolation and complete deletion needed real guarantees.
  • Professionals already trusted its numbers. Energy expenditure, calorie and macro targets had to stay numerically identical.

I significantly improved and engineered the prototype into a real, functional product on infrastructure we own.

Approach & Architecture

The product covers patient records, weight history, anthropometric assessment with photos, energy and macro targets, and a meal-plan editor based on Brazil’s TACO food composition table, with custom foods, recipes and substitutions. It exports branded PDFs in the browser, issues immutable clinical documents verifiable by QR code, and includes Anna, an AI assistant for clinical-practice questions. A free plan (up to 3 active patients) and a Pro plan are billed through Asaas.

Macronutri architecture The browser runs a React single-page app served by Cloudflare Workers. It talks to Supabase over HTTPS with a JWT. Supabase holds PostgreSQL 17 with row level security on every table, Auth with Google OAuth, private Storage with signed URLs, Edge Functions on Deno 2, and pg_cron jobs. Edge Functions call an LLM API with pseudonymized data only, receive signed payment webhooks from Asaas, and send email through Resend. Browser React 19 SPA · PWA TanStack Query · Zod served by Cloudflare Workers HTTPS+JWT Supabase São Paulo · staging + production PostgreSQL 17 · RLS on every table Auth · Google OAuth Storage · private, signed URLs Edge Functions · Deno 2 pg_cron · scheduled jobs AI calls webhook email LLM API pseudonymized data only Asaas payments Resend transactional email
Fig. 1System architecture. The browser never holds a business rule that matters; Postgres does.

Security in the database, not the UI

The free-plan limit moved from the browser into a Postgres trigger with locking, so concurrent requests can’t bypass it. Every table has Row Level Security with per-dietitian isolation. SECURITY DEFINER functions are hardened: fixed search_path, privileges revoked and then granted explicitly.

The payment vs. cancellation race

The Asaas webhook is idempotent, validates signatures on the raw body, and uses optimistic concurrency. Two simultaneous gateway events can no longer cancel the subscription of a customer who paid.

Anna never sees patient names

  • Patients are pseudonymized (P1, P2…). National ID (CPF), phone and email are masked.
  • Clinical scope limits are enforced.
  • A monthly AI quota is reserved in the database and refunded if the AI call fails.
  • A live evaluation suite (eval:anna) tests the assistant.

Near-zero fixed cost

The frontend runs on Cloudflare Workers (Static Assets). I evaluated Vercel Hobby and rejected it, because its terms prohibit commercial use.

Safe migration and LGPD-grade deletion

I built a migration tool from Base44. It is repeatable, runs as a dry run by default, and never resurrects patients deleted on an LGPD request. Patient and account deletion removes Storage files first, then the records.

Security and privacy review

I wrote a formal penetration-testing plan with a threat model covering 12 attack surfaces: SPA, Auth, PostgREST/RLS, Storage, AI, webhook, cron, public verification, PDFs, supply chain, import, and operations/LGPD. Each attack followed the same loop: write a failing test, fix, keep the test green. The tests were validated by mutation. Tools: OWASP ZAP, gitleaks, osv-scanner, actionlint, npm audit.

Reviews caught real bugs, for example:

  • attaching a plan to another dietitian’s patient
  • two simultaneous gateway events cancelling a paying subscription
  • Pro access with no expiry
  • imports resurrecting deleted patients

Hardening also covered stronger Auth settings, a Cloudflare Turnstile captcha, security headers and a strict CSP, EXIF stripped from photos, an automated test that no secrets reach the browser, and no personal data in logs or emails. Storage is private, with per-user policies and short-lived signed URLs. Retention jobs run on pg_cron. Money is stored as integer cents.

Delivery pipeline

CI/CD pipeline A git push runs nine CI jobs: unit, deno, rls, npm audit, osv-scanner, gitleaks, docker, wrangler and visual. Every green push deploys to staging automatically. Production deploys are manual, behind a confirmation gate with a pre-deploy backup. A global DEPLOY_ENABLED kill switch governs both deploys. A scheduled workflow makes daily encrypted backups. git push CI · 9 jobs unitdenorlsnpm auditosv-scannergitleaksdockerwranglervisual green DEPLOY_ENABLED · global kill switch Staging automatic Manual gate confirmation + pre-deploy backup Production Scheduled separately: daily encrypted backups (age).
Fig. 2CI/CD. Staging follows every green push; production needs a person, a confirmation and a fresh backup.

Staging and production are separate Supabase projects in the São Paulo region, with per-environment secrets and Google OAuth. The domain runs on Cloudflare with DNSSEC, Email Routing, SPF and DMARC. Resend sends email; UptimeRobot monitors uptime.

Frontend data-access functions receive their client as a parameter, so they are testable with no global client. A performance budget (≤ 300 KB gzip initial JS) is enforced in CI, and a lint rule blocks heavy imports in the initial bundle.

My Role

I was co-founder and CTO. I made every strategic and architectural decision: the stack, the data model and security model, the privacy and LGPD approach, infrastructure and cost architecture, CI/CD design, and the technical roadmap.

AI coding agents handled the operational implementation. I used Claude Code alongside OpenCode, Antigravity and Gemini in a spec-driven workflow.

Who decided what Felipe writes or approves every spec and plan. AI coding agents implement task by task with tests written first. A separate reviewer agent checks each task. CI runs nine jobs. Felipe reviews the output and makes the final call. Felipe writes or approvesthe spec and plan AI coding agents implement task by task,tests written first Reviewer agent checks each taskseparately CI · 9 jobs tests, RLS, scans,visual regression Felipe reviews the output,makes the final call DECIDES EXECUTES AND CHECKS DECIDES
Fig. 3Who decided what. Agents execute; I decide what ships.
  • I wrote or approved every spec and plan: 31 versioned specs and implementation plans, plus a decision ledger.
  • Agents implemented the work task by task, with tests written before code.
  • A separate reviewer agent checked each task.
  • I reviewed the output and made the final call.
  • Commits are co-authored by Claude, as transparent AI usage.

I also operated the real infrastructure myself: accounts, domain, DNS, secrets, OAuth and deploys.

UX research and design

My UX work at ITRAC taught me to design around real users before writing code. Here that meant interviews and usability tests with dietitians to shape flows and priorities.

  • Numerical parity with the prototype’s calculations, so professionals could keep trusting the numbers they relied on.
  • Accessibility as a requirement: WCAG 2.2 AA, palette contrast verified by automated tests, axe checks in Playwright, keyboard navigation, prefers-reduced-motion respected.
  • A token-based design system: lint blocks any literal colour outside the token file, and visual regression tests run in Playwright.
  • Documented visual identity iterations: a replica of the prototype, then an Apple HIG-inspired look, then a terracotta palette with seven themes, and finally the current “Clinical Blue” identity. The co-founder/CEO provided the logo; I implemented it in code across symbol geometry, icons, favicon and PDFs.

On the business side, I worked with the co-founder/CEO on positioning, pricing analysis against the main competitor, a 90-day go-to-market plan, unit-economics estimates and a legal/tax launch checklist.

Outcome

Macronutri has been live in production with real users since November 1, 2026. Staging went up on October 29 and Google login on October 30.

Engineering by numbers
PeriodOct 13 – Nov 1, 2026 (~3 weeks)
Commits497
Application TypeScript (excluding tests)~17k lines
SQL migrations (forward-only)40
Server functions (Deno / Edge Functions)4
Feature modules / screens13 / ~30
Unit tests / RLS integration tests742 / 238
CI jobs9
Versioned specs & plans31
Initial JS budget≤ 300 KB gzip, enforced in CI
Security findingsLoggedFixed
Critical0—
High22
Medium1917
Low / informational47—

All findings are logged, and there is an incident-response plan.

Stack

  • Frontend: React 19, TypeScript, Vite, Tailwind CSS 4, TanStack Query, React Router, Zod, jsPDF, PWA (Workbox), self-hosted fonts
  • Backend: Supabase (PostgreSQL 17, RLS, Auth, Storage, Edge Functions on Deno 2, pg_cron)
  • AI in the product: LLM assistant with pseudonymization, scope limits and a live eval suite
  • AI development tooling: Claude Code, OpenCode, Antigravity, Gemini
  • Payments / email: Asaas, Resend
  • Infrastructure: Cloudflare Workers, DNS and Email Routing; Docker (rootless nginx); GitHub Actions; UptimeRobot
  • Quality / security: Vitest, Playwright, axe, ESLint, Stylelint, OWASP ZAP, gitleaks, osv-scanner, actionlint, npm audit