ποΈ Setting Up a Flask Application
A great app starts with a solid scaffold. In this lesson you'll build the foundation every later feature rests on: an isolated virtual environment, a project structure that fits your app's size, configuration that adapts to development, testing, and production, and the developer workflow that ties it all together.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Create and activate a virtual environment and manage dependencies
- Choose between single-module, package, and application-factory structures
- Build a testable app with the application factory pattern
- Manage settings with configuration classes and environment variables
- Run, inspect, and debug an app using the Flask CLI and shell
Estimated Time: 35β45 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Scaffold a complete package-structured Flask project with per-environment config.
In This Lesson
Why Structure Matters
Flask deliberately does not impose a project layout β which is freeing until your one-file app hits 800 lines and you can't find anything. A thoughtful structure isn't about tidiness for its own sake; it directly buys you:
- Maintainability β related code lives together, so changes are easy to locate.
- Scalability β the app can grow feature by feature without becoming a tangle.
- Collaboration β teammates work in separate files without stepping on each other.
- Testability β the factory pattern lets tests spin up isolated app instances.
- Clean deployment β configuration and secrets stay out of the code.
π‘ Rule of thumb: match the structure to the app. Start simple and refactor upward as it grows β you don't need blueprints and a factory for a 30-line prototype, but you'll be glad of them at 3,000 lines.
Virtual Environments & Dependencies
A virtual environment is an isolated Python installation for a single project. It keeps each project's packages separate so an upgrade in one app can't break another. Always create one before installing anything.
# Create a virtual environment in a folder named .venv
python -m venv .venv
# Activate it β macOS / Linux
source .venv/bin/activate
# Activate it β Windows (PowerShell)
.venv\Scripts\Activate.ps1
# Install Flask
pip install flask
# Record exact versions so others can reproduce your setup
pip freeze > requirements.txt
# Later, on another machine:
pip install -r requirements.txt
π requirements.txt vs. modern tools
pip + requirements.txt is the universal baseline every Python developer understands. Tools like Poetry or uv add lockfiles and dependency resolution on top, and are worth exploring later β but for this course we stick with venv and pip so nothing is hidden.
β οΈ Never commit your virtual environment
Add .venv/ (and __pycache__/, .env, *.db) to your .gitignore. The environment is rebuildable from requirements.txt; committing it just bloats the repo.
Three Project Structures
As an app grows it typically climbs a ladder of three structures. Here's when to use each.
app.py] -->|grows| B[Package
app/ folder] B -->|grows| C[Factory + Blueprints
create_app] A -.->|prototype| A C -.->|large / team app| C
1. Single module β prototypes & scripts
Everything in one app.py. Perfect for demos and learning.
# app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template("home.html")
2. Package β small to medium apps
Move the app into an app/ package so models, routes, and templates each get their own file.
project/
βββ app/
β βββ __init__.py # creates the app
β βββ routes.py # view functions
β βββ models.py # database models
β βββ static/ # css, js, images
β βββ templates/ # Jinja2 templates
βββ config.py # configuration classes
βββ run.py # entry point
βββ requirements.txt
3. Application factory + blueprints β large / team apps
The app is built by a create_app() function, and routes are grouped into blueprints (auth, main, adminβ¦). This is the target structure for serious projects and the one this course uses going forward.
project/
βββ app/
β βββ __init__.py # create_app() factory
β βββ extensions.py # db, migrate, login_manager objects
β βββ models.py
β βββ main/ # a blueprint
β β βββ __init__.py
β β βββ routes.py
β β βββ templates/main/
β βββ auth/ # another blueprint
β β βββ __init__.py
β β βββ routes.py
β βββ static/
β βββ templates/ # shared base.html
βββ config.py
βββ run.py
βββ requirements.txt
The Application Factory
The application factory is a function β conventionally create_app() β that builds and returns a configured Flask app. Instead of a module-level app = Flask(__name__) global, you construct the app on demand.
# app/__init__.py
from flask import Flask
from app.extensions import db, migrate
from config import config
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Bind extensions to this app
db.init_app(app)
migrate.init_app(app, db)
# Register blueprints
from app.main import bp as main_bp
app.register_blueprint(main_bp)
return app
# app/extensions.py β extension objects live here, unbound
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
β Why this pays off
- Testing: a test can call
create_app("testing")to get a fresh app backed by an in-memory database β completely isolated from your dev data. - Multiple configs: the same code runs in development, testing, and production by passing a different config name.
- No circular imports: extensions live in
extensions.py, so models and routes import them, never the app module.
Configuration Management
Different environments need different settings: debug on locally, a throwaway database in tests, real secrets in production. The clean pattern is a base Config class that each environment subclasses.
# config.py
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, ".env"))
class Config:
"""Settings shared by every environment."""
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-key-change-me")
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or \
"sqlite:///" + os.path.join(basedir, "dev.db")
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
"default": DevelopmentConfig,
}
Secrets and machine-specific values come from a .env file, read at startup by python-dotenv:
# .env β NEVER commit this file
SECRET_KEY=a-long-random-string
DATABASE_URL=sqlite:///app.db
FLASK_DEBUG=1
β οΈ Keep secrets out of version control
Your .env holds passwords and keys β add it to .gitignore immediately. Commit a .env.example with blank values instead, so teammates know which variables to set without ever seeing your real secrets.
The Developer Workflow
Once the scaffold exists, day-to-day work settles into a rhythm. The Flask CLI drives most of it.
# Tell Flask where the app is (or use --app on each command)
export FLASK_APP=run.py
# Run the auto-reloading dev server
flask run --debug
# Open an interactive shell with your app context loaded
flask shell
# Database migrations (with Flask-Migrate)
flask db migrate -m "Add users table"
flask db upgrade
# Run the test suite
pytest
The flask shell command is especially handy β it drops you into Python with the app context already active. Register commonly-used objects so they're waiting for you:
# in create_app(), or run.py
@app.shell_context_processor
def make_shell_context():
return {"db": db, "User": User}
π‘ The tight feedback loop
With --debug, Flask auto-reloads whenever you save a file and shows an interactive traceback in the browser when something breaks. Edit β save β refresh is the whole loop. Just remember: that debugger is a security risk, so it must be off in production.
Full Project Walkthrough
Let's assemble a complete package-structured app end to end. First, the scaffold:
python -m venv .venv
source .venv/bin/activate
pip install flask flask-sqlalchemy flask-migrate python-dotenv
mkdir -p myapp/app/static/css myapp/app/templates
cd myapp
touch app/__init__.py app/routes.py app/models.py \
app/extensions.py config.py run.py .env .gitignore
pip freeze > requirements.txt
The application factory, wiring in the config and models:
# app/__init__.py
from flask import Flask
from app.extensions import db, migrate
from config import config
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
migrate.init_app(app, db)
from app import routes # register view functions
app.register_blueprint(routes.bp)
return app
A model β note it imports db from extensions, avoiding circular imports:
# app/models.py
from datetime import datetime, timezone
from app.extensions import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(120), unique=True, index=True)
created_at = db.Column(
db.DateTime, default=lambda: datetime.now(timezone.utc)
)
def __repr__(self):
return f"<User {self.username}>"
Routes grouped in a small blueprint:
# app/routes.py
from flask import Blueprint, render_template
bp = Blueprint("main", __name__)
@bp.route("/")
def home():
return render_template("home.html", title="Home")
@bp.route("/about")
def about():
return render_template("about.html", title="About")
The entry point, choosing a config from the environment:
# run.py
import os
from app import create_app
app = create_app(os.getenv("FLASK_CONFIG", "default"))
if __name__ == "__main__":
app.run()
Run it with flask --app run run --debug, initialize the database with flask db init && flask db migrate && flask db upgrade, and you have a real, extensible foundation.
Startup output looks like:
* Serving Flask app 'run'
* Debug mode: on
* Running on http://127.0.0.1:5000
* Restarting with stat
Hands-on Exercise
ποΈ Scaffold a Blog Foundation
Objective: Build a package-structured Flask app with an application factory and per-environment config.
Instructions:
- Create a virtual environment and install
flaskandpython-dotenv. - Create the package layout:
app/__init__.py,app/routes.py,config.py,run.py. - In
config.py, defineConfig,DevelopmentConfig, andTestingConfig, plus theconfigdict. - Write a
create_app(config_name)factory that loads the chosen config and registers amainblueprint. - Add
/and/aboutroutes, run the app with--debug, and confirm both pages load. - Create a
.gitignorethat excludes.venv/,__pycache__/, and.env.
π‘ Hint
Keep extension objects (if any) in a separate extensions.py and import them into both the factory and your models. The factory should return the app; run.py calls the factory and holds the entry point.
β Sample solution (factory + config)
# config.py
class Config:
SECRET_KEY = "dev-key-change-me"
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"default": DevelopmentConfig,
}
# app/__init__.py
from flask import Flask, Blueprint, render_template
from config import config
bp = Blueprint("main", __name__)
@bp.route("/")
def home():
return render_template("home.html")
@bp.route("/about")
def about():
return "About this blog."
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config[config_name])
app.register_blueprint(bp)
return app
# run.py
from app import create_app
app = create_app("development")
if __name__ == "__main__":
app.run(debug=True)
π― Quick Quiz
Question 1: What is the main reason to use a virtual environment?
Question 2: What does the application factory pattern give you?
Question 3: Where should secrets like SECRET_KEY and database passwords live?
Summary & Quiz
π Key Takeaways
- Always develop inside a virtual environment and pin dependencies in
requirements.txt. - Match your structure to the app: single module β package β factory + blueprints.
- The application factory (
create_app()) enables testing, multiple configs, and avoids circular imports. - Manage settings with config classes per environment and secrets in a gitignored
.env. - The Flask CLI (
flask run,flask shell,flask db) powers the daily workflow.
π Further Reading
- Flask β Application Factories
- Flask β Configuration Handling
- Flask β Command Line Interface
- Flask-Migrate Documentation
π What's Next?
With a project scaffold in place, we turn to the heart of any web app: routing. Next we explore how Flask maps URLs to view functions, captures dynamic URL parts, and returns every kind of response.
π Foundation laid!
You can scaffold a real Flask project from scratch. Now let's fill it with routes.