Skip to content

Recap

You built FitTrack: a workout tracker with a native Flutter app, a SvelteKit web companion, and a single FastAPI backend, backed by Supabase for authentication and managed Postgres — and, in the last module, you deployed all of it.

Trace it by module and it’s a complete product, not a toy:

  • A uv-managed FastAPI project (Module 1) with a real database defined as migrations (Module 2).
  • An async, configured, structured backend (Module 3) that verifies Supabase JWTs and knows who the caller is (Module 4).
  • A domain layer — SQLAlchemy models, Pydantic schemas, repositories (Module 5) — under a full REST surface: an exercise catalog (Module 6), workout logging (Module 7), and progress aggregations (Module 8).
  • Two clients on that one API: a Flutter app with Riverpod, Supabase auth, and a typed API client (Module 9) that logs sets and shows history (Module 10), and a SvelteKit dashboard reading the same endpoints (Module 11).
  • Tests over the backend and the tracking UI (Module 12), then a containerized API on hosted Supabase with both clients shipped (Module 13).

Everything above collapses into one motion: a user logs a set, and it travels the whole system. Here is that round trip — the same for the Flutter app and the Svelte companion, because they share a backend.

sequenceDiagram
participant C as Client<br/>(Flutter / Svelte)
participant Auth as Supabase Auth
participant API as FastAPI<br/>(the gate)
participant DB as Supabase<br/>Postgres
C->>Auth: sign in
Auth-->>C: JWT
Note over C: user taps "save" on a set
C->>API: POST /workouts (+ Bearer JWT)
API->>API: verify JWT (HS256, aud=authenticated)
API->>DB: INSERT workout + sets (async, one tx)
DB-->>API: rows committed
API-->>C: 201 Created (WorkoutRead)
Note over C: history & progress screens refresh

Read it top to bottom and every module has a place in it. The client got its JWT from Supabase Auth (Module 4). FastAPI verified that token before doing anything (Module 4). The write went in as one async transaction through the domain layer (Module 7). And the response came back as a typed Pydantic shape (Module 5) the client rendered. The clients never touch the database — FastAPI is the only thing that does.

Every one of these was a fork in the road with a real alternative. You can now say why you went the way you did — and what it cost.

DecisionWhy it winsThe tradeoffWhere you made it
FastAPI as the shared backendOne place owns business logic and data access; both clients stay thin and consistent.A hop the clients could have skipped by talking to Supabase directly.Architecture →
Supabase for Auth + managed PostgresYou didn’t build password resets, token issuance, or run a database — you used a managed one and wrote features.A platform dependency; you live within Supabase’s model and pricing.Auth & RLS →
RLS as defense-in-depthIf a token ever reached the database directly, row policies still fence users to their own rows.Policies to maintain that the API path never exercises day to day.Auth & RLS →
One repo, two clientsShared API contract, one source of truth, atomic cross-client changes.A larger repo and two very different build/ship pipelines to keep green.Architecture →
Async SQLAlchemy 2.0Non-blocking DB I/O matches FastAPI’s async model and scales under concurrent requests.The async engine and session dependency are more ceremony than a sync ORM.Async database →
Local-first developmentThe whole stack — Postgres, Auth, migrations — ran on your machine, so production was a config change, not a rewrite.You run the Supabase CLI stack locally instead of developing against the cloud.Schema & migrations →

FitTrack is scoped, not unfinished. Each of these was a conscious “not now” so the course could teach the core end to end instead of sprawling:

  • No training programs or plans. You log what you did; the app doesn’t prescribe what to do next. That’s a whole scheduling and templating domain, left for Next steps →.
  • No social or sharing. No followers, feeds, or shared workouts. Every row belongs to one user, which kept the auth and RLS model simple.
  • No nutrition. Food and calories are an entire second product; FitTrack tracks training only, by design (product scope).
  • No offline sync. The Flutter app assumes a connection and talks to the API live (Module 10); a local cache with conflict resolution is a serious feature of its own.
  • A minimal profile. profiles holds a display_name and little else (the schema) — enough to own workouts, nothing more.

None of these are missing by accident. They’re the natural next features, and the next lesson → shows exactly where each one plugs into what you already built.

You built a real, deployed, two-client fitness tracker on a single FastAPI backend and managed Supabase, and you can now trace a logged set through client → Supabase Auth → FastAPI → Postgres → back without hand-waving. More than the code, you leave with decisions you can defend: why FastAPI is the gate, why Supabase, why RLS is a backstop rather than the front door, why one repo, why async, why local-first — each with the tradeoff it carries. And you know exactly what you didn’t build and why. Next, Next steps → turns those deliberate omissions into your roadmap — the concrete features to add, what each one teaches, and where it plugs into the system you already have.