How API routes, functions, and the database all connect
A URL like /entries returns an HTML webpage.
A URL like /api/entries returns raw data β usually JSON.
The /api/ prefix is just a naming convention. It has no special meaning to Flask,
no folder on disk, and no section in memory. It's purely a signal to developers that
"this URL gives you data, not a page."
/entries
Returns entries.html β a full webpage the browser renders for the user.
/api/entries
Returns [{"id":1,"content":"..."},...] β JSON data that JavaScript uses.
This app has 72 routes total β 35 page routes and 37 API routes β all defined
in the single file app.py.
When the Flask app starts up, it reads every @app.route decorator
in app.py and builds an internal URL map β a dictionary that
maps URL patterns + HTTP methods to Python functions. It lives in RAM for the entire
lifetime of the process.
Each value in the map is a reference to a Python function object stored on the heap. When a request arrives, Flask looks up the URL and method, finds the function reference, and calls it.
When Python loads app.py, it compiles each function to bytecode
and stores a function object on the heap. If you print the function you see its address:
0x7f3a1b2c is a real address in RAM β it changes every time the process restarts
because Python allocates wherever there is free space.
| Memory region | What's stored there |
|---|---|
| Heap | The function object itself (name, bytecode reference, default args). Lives here for the whole process. |
| Code segment | The compiled bytecode instructions that the function runs. |
| Stack | Local variables (like entries, start_date) β only exist while the function is actively running, then gone. |
request.args.get() parses optional parameters out of the URL query string. If not present, they are None.db object β this is where the SQL actually runs.jsonify() converts the Python list of dicts into a JSON string and sets the correct HTTP Content-Type header.
A URL can carry parameters after a ? β called the query string.
Flask parses these automatically and makes them available via request.args.
All three values are then passed straight into db.get_entries() as arguments,
where they become filters in the SQL WHERE clause.
At the top of app.py, one Database object is created:
Every route function shares this single db object.
The Database class lives in database.py and knows whether it's
running against SQLite (local dev) or PostgreSQL (Heroku), and adjusts its
SQL accordingly.
db.get_entries() β SELECT, returns list of dictsdb.add_entry() β INSERT, returns new row iddb.update_entry() β UPDATE, modifies existing rowdb.delete_entry() β DELETE, removes row by idfetch('/api/entries?limit=50')
/api/entries in its URL map
request.args.get('limit') extracts 50 from the URL
[{"id":1,"content":"...","pain_level":3}, ...]
jsonify(entries) converts the list to a JSON string
await res.json() parses it and the page renders the entries
| File | Responsibility | Example |
|---|---|---|
| app.py | Handle HTTP: read URL params, call db or external services, return JSON/HTML | get_entries() reads the URL, calls db, returns jsonify() |
| database.py | Run SQL: build queries, talk to SQLite or PostgreSQL, return Python data structures | db.get_entries() builds SELECT, runs it, returns list of dicts |
This separation means you could swap out the database engine (e.g. replace PostgreSQL with MySQL)
by only changing database.py β none of the route functions in app.py would need to change.
This is called the Repository Pattern.