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/.
# 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.
# 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.
# 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.
# 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.
Project Layout, Package Structure, Separation of Concerns, Circular Imports, Best Practices