Skip to main content

Flask App Structure

When you first start building Flask applications, you might be tempted to put all your code in a single file. While this works for tiny applications, it quickly becomes unmanageable as your project grows. In this tutorial, we'll explore how to structure Flask applications for better maintainability, testability, and scalability.

Introduction

Flask is intentionally designed as a microframework, which means it doesn't enforce any particular project structure on developers. This flexibility is powerful, but it also means you need to make deliberate decisions about how to organize your code.

A well-structured Flask application makes it easier to:

  • Add new features
  • Fix bugs
  • Collaborate with other developers
  • Write tests
  • Deploy your application

Let's explore different approaches to structuring Flask applications, starting from simple to more complex.

Single-File Application

For very small applications or prototypes, a single-file structure might be sufficient:

python
# app.py
from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/')
def home():
return render_template('home.html')

@app.route('/about')
def about():
return render_template('about.html')

if __name__ == '__main__':
app.run(debug=True)

This simple structure works well for:

  • Learning Flask basics
  • Building quick prototypes
  • Simple applications with just a few routes

However, as your application grows, this approach becomes problematic. Let's look at better structures for larger applications.

Basic Package Structure

For slightly larger applications, organizing code into a package structure provides better separation of concerns:

my_flask_app/
├── app/
│ ├── __init__.py
│ ├── routes.py
│ ├── models.py
│ ├── forms.py
│ ├── static/
│ └── templates/
├── config.py
├── requirements.txt
└── run.py

Let's see how this works:

python
# app/__init__.py
from flask import Flask

app = Flask(__name__)
app.config.from_object('config')

from app import routes
python
# app/routes.py
from app import app
from flask import render_template

@app.route('/')
def home():
return render_template('home.html')

@app.route('/about')
def about():
return render_template('about.html')
python
# config.py
DEBUG = True
SECRET_KEY = 'your-secret-key'
python
# run.py
from app import app

if __name__ == '__main__':
app.run()

This structure separates your application code from the entry point (run.py). The app package contains all your application code, and config.py contains configuration variables.

Blueprint-Based Structure

As applications grow even larger, using Flask blueprints helps organize code by feature or functionality:

my_flask_app/
├── app/
│ ├── __init__.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── static/
│ ├── templates/
│ └── views/
│ ├── __init__.py
│ ├── auth.py
│ └── main.py
├── config.py
├── requirements.txt
└── run.py

Here's how blueprints work:

python
# app/views/main.py
from flask import Blueprint, render_template

main = Blueprint('main', __name__)

@main.route('/')
def home():
return render_template('main/home.html')

@main.route('/about')
def about():
return render_template('main/about.html')
python
# app/views/auth.py
from flask import Blueprint, render_template, redirect, url_for

auth = Blueprint('auth', __name__, url_prefix='/auth')

@auth.route('/login')
def login():
return render_template('auth/login.html')

@auth.route('/register')
def register():
return render_template('auth/register.html')
python
# app/__init__.py
from flask import Flask

def create_app(config_name='default'):
app = Flask(__name__)

# Load configuration
from config import config
app.config.from_object(config[config_name])

# Register blueprints
from app.views.main import main
from app.views.auth import auth

app.register_blueprint(main)
app.register_blueprint(auth)

return app
python
# run.py
from app import create_app

app = create_app()

if __name__ == '__main__':
app.run()

Blueprints allow you to:

  • Group related functionality (like authentication, API endpoints, etc.)
  • Reuse code across different applications
  • Keep your codebase organized as it grows

Application Factory Pattern

For even more flexibility, especially when writing tests or running multiple instances of the same application, the Application Factory pattern is recommended:

python
# app/__init__.py
from flask import Flask

def create_app(config_name='default'):
app = Flask(__name__)

# Load configuration
from config import config
app.config.from_object(config[config_name])

# Initialize extensions
from app.extensions import db, migrate
db.init_app(app)
migrate.init_app(app, db)

# Register blueprints
from app.views.main import main_blueprint
from app.views.auth import auth_blueprint

app.register_blueprint(main_blueprint)
app.register_blueprint(auth_blueprint)

return app

This pattern allows you to create multiple instances of your application with different configurations, which is particularly useful for testing.

Real-World Example: Todo Application

Let's implement a simple Todo application using the blueprint structure:

todo_app/
├── app/
│ ├── __init__.py
│ ├── extensions.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── todo.py
│ ├── static/
│ │ └── css/
│ │ └── style.css
│ ├── templates/
│ │ ├── base.html
│ │ └── todos/
│ │ ├── index.html
│ │ └── create.html
│ └── views/
│ ├── __init__.py
│ └── todos.py
├── config.py
├── requirements.txt
└── run.py

Here's how the implementation might look:

python
# app/extensions.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
python
# app/models/todo.py
from app.extensions import db
from datetime import datetime

class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=True)
completed = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)

def __repr__(self):
return f'<Todo {self.id}: {self.title}>'
python
# app/views/todos.py
from flask import Blueprint, render_template, request, redirect, url_for
from app.extensions import db
from app.models.todo import Todo

todos_bp = Blueprint('todos', __name__, url_prefix='/todos')

@todos_bp.route('/')
def index():
todos = Todo.query.all()
return render_template('todos/index.html', todos=todos)

@todos_bp.route('/create', methods=['GET', 'POST'])
def create():
if request.method == 'POST':
title = request.form.get('title')
description = request.form.get('description')

if title:
todo = Todo(title=title, description=description)
db.session.add(todo)
db.session.commit()
return redirect(url_for('todos.index'))

return render_template('todos/create.html')

@todos_bp.route('/<int:id>/toggle', methods=['POST'])
def toggle(id):
todo = Todo.query.get_or_404(id)
todo.completed = not todo.completed
db.session.commit()
return redirect(url_for('todos.index'))
python
# app/__init__.py
from flask import Flask

def create_app(config_name='default'):
app = Flask(__name__)

# Load configuration
from config import config
app.config.from_object(config[config_name])

# Initialize extensions
from app.extensions import db
db.init_app(app)

# Register blueprints
from app.views.todos import todos_bp
app.register_blueprint(todos_bp)

# Create database tables
with app.app_context():
db.create_all()

return app
python
# config.py
import os

class Config:
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key')
SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'

class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db'

class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
python
# run.py
from app import create_app

app = create_app()

if __name__ == '__main__':
app.run()

This Todo application demonstrates:

  • Blueprint organization
  • Application factory pattern
  • Database models with SQLAlchemy
  • Configuration management
  • Separation of concerns

Best Practices for Flask Application Structure

Here are some best practices to follow when structuring your Flask applications:

  1. Separation of Concerns: Separate your code into logical components like models, views, forms, and utilities.

  2. Blueprint Organization: Group related functionality into blueprints, either by feature (auth, admin, api) or by resource (users, posts, comments).

  3. Configuration Management: Use different configurations for development, testing, and production environments.

  4. Application Factory Pattern: Create your Flask application using a factory function to enable easier testing and multiple instances.

  5. Extensions Initialization: Initialize Flask extensions outside of your application factory, then initialize them with your app inside the factory.

  6. Keep Templates and Static Files Organized: Organize templates and static files by blueprint or feature.

  7. Tests Directory: Keep tests separate from your application code, typically in a tests/ directory at the project root.

Summary

A well-structured Flask application makes development and maintenance much easier as your project grows. While Flask gives you the freedom to structure your application however you want, following established patterns like blueprints and the application factory can save you time and headaches in the long run.

As your Flask applications become more complex, consider these different structural approaches:

  • Single-file for tiny applications and prototypes
  • Basic package structure for small applications
  • Blueprint-based organization for medium-sized applications
  • Application factory pattern for large applications and when writing tests

Remember, the best structure depends on your specific needs, and it's okay to evolve your structure as your application grows.

Additional Resources

Exercises

  1. Convert a single-file Flask application into a package structure with separate files for routes, models, and configuration.
  2. Create a Flask application with two blueprints: one for user authentication and another for a blog feature.
  3. Refactor an existing Flask application to use the application factory pattern.
  4. Create a simple Flask application with proper structure that includes:
    • User authentication
    • Database models
    • CRUD operations
    • Blueprint organization

By mastering Flask's application structure, you'll be well on your way to building maintainable, scalable web applications with Flask!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)