๐ŸฅฆNdiro
Chapter 1 of 13

One page load, end to end

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.

The journey of GET /log

browser                      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.

๐Ÿ˜ From your world

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.

Three languages, one page

The browser natively runs exactly three languages, and they have strictly separated jobs:

LanguageJobChapter
HTMLStructure and meaning โ€” "there is a form here, with a text area and a save button"HTML
CSSAppearance and layout โ€” "cards have rounded corners; in light mode the background is cream"CSS
JavaScriptBehavior โ€” "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.

Where everything lives

FileLayerWhat's in it
app.pybackendEvery route (URL โ†’ Python function). The table of contents of the whole app.
config.pybackendEnvironment loading, constants, the nutrient catalog, the fiber guide.
auth.pybackendGoogle OAuth, session โ†’ user resolution, the access guards.
db.pybackendAll DynamoDB and S3 access, plus the photo cache.
ai.py, autolog.pybackendOpenAI estimators; the async photo pipeline (the capstone chapter).
templates/base.htmlbothThe shared page skin: theme tokens, menu, the blocks pages fill in.
templates/log.html etc.frontendOne template per page: its HTML, its page-specific CSS, its JavaScript.
tests/bothPlain-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.

Server-rendered shell + JSON data

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.

๐Ÿ”ฌ Try it

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.

๐Ÿ