Project Organization

Difficulty: Intermediate

A well-organized Flask project makes development, testing, and collaboration smoother. While Flask does not enforce any particular project layout, the community has converged on patterns that work well for applications of different sizes.

For small applications (prototypes, simple APIs), a single-file structure works fine. Everything goes in app.py: routes, configuration, and maybe a few helper functions. The templates/ and static/ directories sit alongside it. This is perfect for learning, tutorials, and microservices with a handful of endpoints.

Medium applications benefit from splitting into a few modules. Separate your routes into a views.py, models into models.py, forms into forms.py, and configuration into config.py. The main app.py ties everything together. This structure scales to maybe 20-30 routes before it gets unwieldy.

Large applications use the package structure with the application factory pattern. The Flask application is a Python package (a directory with __init__.py) rather than a single file. Blueprints organize routes into logical groups. Extensions are initialized in a separate module. Each blueprint can have its own templates subdirectory. This scales to hundreds of routes and multiple developers.

The separation of concerns principle guides how you split code. Models define data structures and database interactions. Views handle HTTP request/response logic. Templates handle presentation. Forms handle input validation. Services contain business logic. Utils provide helper functions. Config manages environment-specific settings.

Circular imports are a common problem in Flask projects. They occur when module A imports from module B and module B imports from module A. The most common case is views importing the app object and the app module importing views. The application factory pattern and the extension init_app pattern solve this by deferring imports and initialization.

The tests directory should mirror the source structure. If you have views/auth.py, create tests/test_auth.py. Use pytest fixtures for the app, client, and database setup. Organize fixtures in conftest.py files at appropriate levels.

A production Flask project also includes: requirements.txt or pyproject.toml for dependencies, a Dockerfile for containerization, a .github/workflows directory for CI/CD, a migrations directory for database migrations (managed by Flask-Migrate), and documentation in docs/.

Code examples

Production Project Layout

# Recommended project structure
my_project/
    app/
        __init__.py          # create_app factory
        extensions.py         # db, login_manager, mail, etc.
        models/
            __init__.py
            user.py
            post.py
        views/
            __init__.py       # import all blueprints
            main.py           # main blueprint
            auth.py           # auth blueprint
            api/
                __init__.py
                users.py
                posts.py
        services/
            __init__.py
            email.py
            payment.py
        forms/
            __init__.py
            auth.py
            post.py
        templates/
            base.html
            main/
            auth/
        static/
            css/
            js/
            images/
    tests/
        conftest.py
        test_auth.py
        test_api.py
    migrations/
    config.py
    requirements.txt
    Dockerfile
    .env
    .gitignore

This layout separates concerns: models for data, views for HTTP handling, services for business logic, forms for validation. The app/ package contains the create_app factory. Tests mirror the source structure.

The __init__.py Factory

# app/__init__.py
from flask import Flask
from app.extensions import db, login_manager, migrate, mail
from config import config

def create_app(config_name='development'):
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    # Initialize extensions
    db.init_app(app)
    login_manager.init_app(app)
    migrate.init_app(app, db)
    mail.init_app(app)

    # Register blueprints
    from app.views.main import main_bp
    from app.views.auth import auth_bp
    from app.views.api import api_bp

    app.register_blueprint(main_bp)
    app.register_blueprint(auth_bp, url_prefix='/auth')
    app.register_blueprint(api_bp, url_prefix='/api/v1')

    # Register error handlers
    from app.errors import register_error_handlers
    register_error_handlers(app)

    # Register CLI commands
    from app.cli import register_commands
    register_commands(app)

    return app

The factory imports blueprints inside the function to avoid circular imports. Extensions are imported from a separate module. Error handlers and CLI commands are also registered here for a clean setup.

Extensions Module

# app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_mail import Mail

# Create extension instances without app
db = SQLAlchemy()
login_manager = LoginManager()
migrate = Migrate()
mail = Mail()

# Configure login manager
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info'

# Other modules can import these:
# from app.extensions import db
# This works because extensions are created at import time
# but bound to the app later via init_app()

Creating extensions in a dedicated module avoids circular imports. Any module can import db, login_manager, etc. without importing the app. The factory calls init_app() to bind them to the app.

Blueprint Views Module

# app/views/__init__.py
# Central place to import all blueprints
from app.views.main import main_bp
from app.views.auth import auth_bp

# app/views/auth.py
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required
from app.extensions import db
from app.models.user import User
from app.forms.auth import LoginForm, RegisterForm

auth_bp = Blueprint('auth', __name__, template_folder='../templates/auth')

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            login_user(user)
            next_page = request.args.get('next')
            return redirect(next_page or url_for('main.index'))
        flash('Invalid email or password', 'error')
    return render_template('login.html', form=form)

@auth_bp.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('main.index'))

Each blueprint module handles a specific feature. It imports models and extensions directly (no circular import because they do not import the app). The template_folder can be customized per blueprint.

Key points

Concepts covered

Project Layout, Package Structure, Separation of Concerns, Circular Imports, Best Practices