A Flask boilerplate following the MVC pattern designed as a base skeleton for new Python web applications. The primary goal is clarity and correctness over complexity.
The app is created via create_app(config_name) in project/__init__.py. Never import a global
app object — always go through the factory. The factory:
- Loads the correct
Configclass fromproject/config.py - Initialises Flask extensions (e.g.
DebugToolbarExtension) - Registers all Blueprints
project/
├── __init__.py # create_app() factory
├── config.py # Config classes (Development / Testing / Production)
├── controllers/ # Flask Blueprints — one file per resource
│ └── printer.py # printer_bp Blueprint
├── models/ # Plain Python classes, no ORM currently
│ └── printer.py
├── static/
│ └── css/style.css
└── templates/
├── layout/
│ ├── layout.html # base template
│ └── macros.html # reusable Jinja2 macros (render_error, etc.)
└── printer/
├── index.html
└── print.html
Every controller is a Blueprint registered in create_app(). Do not use the old glob
auto-import pattern. To add a new resource:
- Create
project/controllers/<resource>.pywith a<resource>_bp = Blueprint(...). - Register it in
create_app():from project.controllers.<resource> import <resource>_bp app.register_blueprint(<resource>_bp)
- PEP 8 throughout.
flake8enforces it withmax-line-length = 100. - Module filenames are always
snake_case.py— never PascalCase. - Imports at the top of every file — never inside functions or routes.
- Classes use PascalCase; functions and variables use snake_case.
- All runtime config comes from environment variables. No secrets in source code.
SECRET_KEYis required at startup; the app raisesValueErrorimmediately if missing.- Use
FLASK_ENVto select the config profile:development(default),testing,production. - Reference
project/config.pybefore adding any new config key.
- One Blueprint per resource in
project/controllers/. - Always use POST/Redirect/GET after a successful form submission to prevent double-posts:
return redirect(url_for('<blueprint>.<view>'))
- Form classes live in the same file as the Blueprint that owns them.
- Keep controllers thin — delegate logic to model classes.
- Plain Python classes. No Flask imports except
flash/current_appwhere unavoidable. - Models do not duplicate validation that the form layer already enforces.
- All templates extend
layout/layout.html. - Reusable Jinja2 macros belong in
layout/macros.htmland are imported with{% from "layout/macros.html" import <macro> %}. - Use
url_for('blueprint_name.view_name')— never hardcode URLs. - Write valid HTML5 (
<!doctype html>,<html lang="en">,<meta charset="utf-8">).
| Variable | Required | Default | Description |
|---|---|---|---|
SECRET_KEY |
Yes | — | Flask session / CSRF signing key |
FLASK_ENV |
No | development |
Config profile to load |
PORT |
No | 8080 |
Port for the development server |
Generate a secure key with:
python -c "import secrets; print(secrets.token_hex(32))"cp .env.example .env # fill in SECRET_KEY at minimum
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-test.txtRun the dev server:
SECRET_KEY=<key> python runserver.py
# or: make run (reads from .env if you source it first)SECRET_KEY=test-secret pytest tests/ -v --cov=project- Tests live in
tests/. Fixtures (app,client) are intests/conftest.py. - Use
TestingConfigwhich setsWTF_CSRF_ENABLED = Falseand a fallbackSECRET_KEY. - Every new controller must have a corresponding test file
tests/test_<resource>.py. - Cover: GET renders correct template, POST with valid data redirects, POST with invalid data returns the form with errors.
- The
conftest.pyappfixture callscreate_app('testing')— do not create a new app instance inside individual test files.
-
project/models/<resource>.py— model class -
project/controllers/<resource>.py— Blueprint + form + routes - Register Blueprint in
project/__init__.py → create_app() -
project/templates/<resource>/— HTML templates -
tests/test_<resource>.py— test suite - Export any new env vars in
.env.example
This boilerplate is minimal by design. The following are not wired up yet but can be added when needed:
- Database / SQLAlchemy — postgres is already running in
docker-compose.ymlon port 5432 (flask_devdb, userflask, passwordflask). Add Flask-SQLAlchemy + Flask-Migrate and setDATABASE_URLin.envto connect. - Authentication (Flask-Login, Flask-Security)
- REST API layer (Flask-RESTful)
- Task queue (Celery / Flower)
- Frontend asset pipeline (Flask-Assets, cssmin, jsmin)
If you need one of these, add it in isolation with its own Blueprint and tests.