Skip to main content

πŸ—οΈ 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.

flowchart LR A[Single module
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:

  1. Create a virtual environment and install flask and python-dotenv.
  2. Create the package layout: app/__init__.py, app/routes.py, config.py, run.py.
  3. In config.py, define Config, DevelopmentConfig, and TestingConfig, plus the config dict.
  4. Write a create_app(config_name) factory that loads the chosen config and registers a main blueprint.
  5. Add / and /about routes, run the app with --debug, and confirm both pages load.
  6. Create a .gitignore that 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

πŸš€ 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.