Let's trace exactly what happens when a signed-in user opens the log page. Everything else in this book hangs off this one journey; if you internalize it, no file in the repo will ever feel mysterious.
GET /logbrowser server (one Docker container)
โ
โ 1. HTTPS request โโโโโโโถ reverse proxy (Caddy, or Render's LB)
โ GET /log โ terminates TLS, adds X-Forwarded-* headers
โ Cookie: session=โฆ โผ
โ gunicorn: 1 worker process, 8 threads
โ โ speaks WSGI to the app
โ โผ
โ Flask routes the path โ log_page() in app.py
โ โ @approved_required reads the users table
โ โ render_template('log.html', โฆ) fills in Jinja
โ 2. HTML response โโโโโโโ โผ โ one big HTML string
โ
โ 3. parses HTML โ builds the DOM tree
โ 4. applies the CSS in <style> โ paints the page shell
โ 5. runs the <script> blocks
โ โ
โ โ 6. fetch('/api/meals?days=7&anchor=โฆ') โโโถ same path as step 1,
โ โ but returns JSON
โ โผ 7. JSON โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ from DynamoDB queries
โ 8. JavaScript builds meal cards out of the JSON and
โ inserts them into the DOM โ now you see your meals
Two round trips, two kinds of response. The first returns HTML: the page's structure, styling, and code, rendered on the server with everything the server already knows (who you are, which nutrient you track, whether AI is configured). The second returns JSON: pure data, which JavaScript running in the browser turns into visible elements.
This is a client-server RPC system. The browser is the client
runtime; fetch() is the RPC call; JSON is the wire
format; HTTP status codes are the response codes. The twist: the
server ships the client its code (HTML/CSS/JS) on first
contact, and the client is untrusted โ anyone can craft any request
with curl, so every check must live server-side.
"Frontend validation" is UX, never security.
The browser natively runs exactly three languages, and they have strictly separated jobs:
| Language | Job | Chapter |
|---|---|---|
| HTML | Structure and meaning โ "there is a form here, with a text area and a save button" | HTML |
| CSS | Appearance and layout โ "cards have rounded corners; in light mode the background is cream" | CSS |
| JavaScript | Behavior โ "when the save button is clicked, POST the form to /api/meals and reload the list" | JavaScript |
There is a fourth language in this repo that never reaches the browser: Jinja, the server-side template language that generates the HTML (the Jinja chapter). View the source of any page (Ctrl+U) and you will find no Jinja in it โ only its output.
| File | Layer | What's in it |
|---|---|---|
app.py | backend | Every route (URL โ Python function). The table of contents of the whole app. |
config.py | backend | Environment loading, constants, the nutrient catalog, the fiber guide. |
auth.py | backend | Google OAuth, session โ user resolution, the access guards. |
db.py | backend | All DynamoDB and S3 access, plus the photo cache. |
ai.py, autolog.py | backend | OpenAI estimators; the async photo pipeline (the capstone chapter). |
templates/base.html | both | The shared page skin: theme tokens, menu, the blocks pages fill in. |
templates/log.html etc. | frontend | One template per page: its HTML, its page-specific CSS, its JavaScript. |
tests/ | both | Plain-script stub tests that run the real app against fakes โ no AWS, no network. |
Notice what is absent: no JavaScript framework (React, Vue),
no build step, no node_modules, no CSS preprocessor, no
bundler. Each page's CSS and JS live inside its template. That is a
deliberate choice at this scale โ the entire frontend toolchain is a
text editor, and "view source" in the browser shows you the truth.
Web apps sit on a spectrum. At one end, classic server rendering: every click reloads a full page, forms POST and the server responds with new HTML. At the other, the single-page app (SPA): the server serves one near-empty HTML file plus a megabyte of JavaScript that renders everything and talks JSON from then on.
Ndiro deliberately sits in the middle. The server renders each page's
static shell โ headers, the form, anything derived from config or
identity โ and JavaScript fetches only the dynamic data (meals) as JSON.
You get fast first paint and simple code, without a framework. Watch for
the seam as you read templates/log.html: everything inside
{{ โฆ }} was decided on the server at render time;
everything built by document.createElement was decided in
the browser afterwards.
Open the log page, press F12, and pick the
Network tab. Reload. You'll see the document request
(/log, type document) followed by
/api/meals?days=7&anchor=โฆ (type
fetch/xhr). Click each and inspect the request headers,
the response body, and the timing. That's steps 1โ8 above, live.