How Websites, Mobile Apps, Backends, and Databases Work Together
An application feels like one thing to a user, but it is a conversation between layers. Understanding that conversation makes AI-generated bugs much less mysterious.
Follow one transaction
Imagine Sam signs in and records a $24 grocery expense.
- The interface displays a form.
- Local state remembers the typed amount, category, and date.
- Validation rejects missing or impossible values before sending them.
- The client sends an HTTP request containing JSON data and proof of identity.
- The backend or managed data API verifies the session.
- Authorization rules confirm Sam may create a row owned by Sam.
- The database stores the transaction.
- The response returns a success result or a structured error.
- The interface refreshes the list and totals.
Every step can fail independently. Good software exposes a useful state instead of freezing or pretending the save worked.
Client: the untrusted interface
The web browser and mobile app are clients. They handle interaction and presentation. They are untrusted because users can inspect, modify, or imitate what a client sends.
Client-side validation improves experience but is not a security boundary. A determined caller can skip the form and send a request directly. Important rules must also exist in trusted server code or database policies.
Safe public configuration, such as a project URL or a publishable client key intended for that environment, can be included according to provider guidance. Administrative credentials, service-role keys, private API keys, and database passwords cannot.
HTTP and JSON
HTTP is a request-response protocol. Common methods are:
GETreads data.POSTcreates data or starts an action.PATCHupdates part of a record.DELETEremoves a record.
JSON is a text representation of data:
{
"type": "expense",
"amount": 24,
"category_id": "category-id",
"occurred_on": "2026-08-01",
"note": "Groceries"
}Money needs an explicit representation. Many systems store minor units—2400 cents instead of a floating-point 24.00—or use a precise decimal type. Always store currency separately when multiple currencies are possible.
API: the contract
An API defines allowed inputs, outputs, authentication, and errors. Think of it as a contract, not simply a URL.
A useful create-transaction contract says which fields are required, valid ranges, who owns the new record, what success returns, and how validation or authorization errors appear. FastAPI can generate interactive documentation from Python types, while Supabase exposes data APIs constrained by PostgreSQL policies.
Clients should not depend on a database's accidental internal shape. A deliberate API or well-managed data access layer makes future changes safer.
Authentication and sessions
After sign-in, the identity provider issues session information. The client sends an access token with protected requests. Trusted code verifies its signature, issuer, audience, and expiry according to the provider's documented method.
Do not treat a user ID in the request body as proof. The verified token determines the current user. The application then checks ownership or role before reading or changing data.
Database: durable state
The database persists information after a browser closes or a phone restarts. A relational design might include:
profiles: one row per account.categories: a category owner and display properties.transactions: owner, category, type, amount, date, and note.budgets: owner, category, month, and limit.
Foreign keys prevent references to missing records. Check constraints reject invalid types or amounts. Indexes help frequent filters. Row Level Security restricts which rows a signed-in user may access.
Direct-to-Supabase versus custom API
In the web and mobile projects, the client can use the Supabase SDK with a publishable key and the user's session. RLS remains the authorization boundary. This removes much custom server code and is appropriate for a small product with clear data rules.
In the FastAPI project, clients call our API. FastAPI validates requests and applies rules before PostgreSQL access. This approach is useful when multiple clients share complex business logic, external secrets are required, or you need a controlled public contract.
Neither approach is automatically safer. Safety depends on correct configuration, least privilege, validation, testing, dependency maintenance, and monitoring.
Loading, empty, error, and success
Every data-driven screen needs at least four visible states:
- Loading: work is in progress; prevent accidental duplicate submissions.
- Empty: the request succeeded but there are no records; offer a next action.
- Error: the operation failed; preserve input and explain recovery.
- Success: show fresh confirmed data, not an unsupported assumption.
Offline is a separate state. Decide whether to block writes, queue them, or support local-first synchronization. This course begins with clear network-required behavior before attempting offline sync.
Prompt: trace a data flow
Inspect this project without editing. Trace the "create transaction" flow from
the form to persistent storage and back to the visible list. Name each file,
function, request, validation rule, authentication check, authorization rule,
and error state. Mark anything you inferred rather than verified. Identify where
a second user might access the record if configuration is wrong.This is a powerful debugging prompt for Codex, Claude Code, or Cursor because it demands evidence instead of a speculative fix.
Manual scenario
Draw five boxes on paper: Client, Auth, API/Policy, Database, Client. Add arrows for sign-in, create, result, and refresh. Then repeat the flow for:
- Missing amount.
- Expired session.
- No network.
- Database rejection.
- A user requesting someone else's transaction.
If you cannot describe which layer rejects a case, the architecture has an unresolved rule.
Completion checklist
- I can trace one record from form to database and back.
- I understand why the client is untrusted.
- I know the difference between an API key and a user session.
- I can name loading, empty, error, and success behavior.
- I understand direct Supabase access versus a custom API.