Flask Directory Structure
Flask lets you start with a single file, but real apps need structure.
A clean structure makes it easier to:
- scale your codebase
- write tests
- add Blueprints
- deploy reliably
Option 1: single-file starter (learning only)
Section titled “Option 1: single-file starter (learning only)”app.pyThis is okay for “Hello World”, but it gets messy fast.
Option 2: recommended starter package layout
Section titled “Option 2: recommended starter package layout”myapp/
app.py
templates/
home.html
static/
styles.cssFlask will auto-discover templates/ and static/ relative to your app.
Option 3: scalable layout (closer to production)
Section titled “Option 3: scalable layout (closer to production)”myapp/
__init__.py # create_app() lives here
routes.py # routes / views
models.py # database models
templates/
static/
config.py
wsgi.py # entrypoint for gunicornWhy separate wsgi.py?
Section titled “Why separate wsgi.py?”A typical production command is:
gunicorn "wsgi:app"
So wsgi.py is an easy “server entry point”.
What’s next
Section titled “What’s next”In Phase 2 you’ll learn routing and views in depth.
In Phase 7 you’ll learn:
- Blueprints
- application factory pattern
Those concepts make Option 3 feel natural.
The paths Flask assumes before you configure anything
Section titled “The paths Flask assumes before you configure anything”Flask(__name__) derives every location from where that module lives. Measured on a
fresh app:
flowchart TD A["Flask(__name__)"] --> R["root_path = the folder containing your module"] R --> S["static_folder = root_path/static
served at /static"] R --> T["template_folder = 'templates'
relative to root_path"] R --> I["instance_path = root_path/instance
for secrets and the database"]
| attribute | measured value |
|---|---|
app.root_path | the directory of the module that called Flask() |
app.static_folder | <root_path>/static — an absolute path |
app.static_url_path | /static |
app.template_folder | templates — a relative path |
app.instance_path | <root_path>/instance |
Note the inconsistency, because it causes real confusion: static_folder comes back
absolute and template_folder comes back relative. Both are resolved against
root_path; only their representation differs.
myapp/
app.py <- root_path is this folder
static/ <- served at /static, no code needed
style.css
templates/ <- render_template looks here
index.html
instance/ <- config and sqlite that must NOT be in git
config.pyWhen one file stops being enough
Section titled “When one file stops being enough”A single app.py is correct until routes multiply or you need more than one app
instance (say, one for tests). The next step is a package with an application factory:
myapp/
__init__.py <- create_app() lives here
routes/
__init__.py
users.py <- a Blueprint
models.py
templates/
static/
tests/
config.pyfrom flask import Flask
def create_app(config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(config or "config.Default")
from .routes.users import bp as users_bp
app.register_blueprint(users_bp, url_prefix="/users")
return appThe factory matters because a module-level app = Flask(__name__) is created at import
time with one fixed configuration. A factory lets tests build an app with a temporary
database and throw it away, which is the difference between testable and not.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
What determines app.root_path?
Flask derives root_path from the module you pass as __name__, and static_folder, template_folder and instance_path are all resolved from it. That is why moving app.py moves everything.
pch.quizShowAnswer
B — the folder containing the module that called Flask(__name__) — Flask derives root_path from the module you pass as __name__, and static_folder, template_folder and instance_path are all resolved from it. That is why moving app.py moves everything.
-
What is the instance/ folder for?
instance_path sits outside the package, is created on demand, and is the intended home for secrets and the database. It belongs in .gitignore.
pch.quizShowAnswer
B — configuration and data files such as the SQLite database that must not be committed — instance_path sits outside the package, is created on demand, and is the intended home for secrets and the database. It belongs in .gitignore.
-
Why prefer an application factory over a module-level app = Flask(__name__)?
A module-level app is created once at import with one fixed configuration. A factory makes configuration a parameter, which is what makes the app testable.
pch.quizShowAnswer
C — it lets you build an app per configuration, so tests can create one with a temporary database and discard it — A module-level app is created once at import with one fixed configuration. A factory makes configuration a parameter, which is what makes the app testable.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading