Flask Application Factory Pattern: When to Use create_app() vs Global app Instance
When building Flask applications, we often start with a single file—a script that works quickly and directly. But as requirements grow, that single file becomes increasingly difficult to manage due to growing dependencies and configuration. This is the classic challenge of scaling: how do we maintain simplicity as complexity increases?
Historically, software engineering solved this through modularity and separation of concerns. The Flask application factory pattern applies this same principle: it separates the creation of the application from its configuration and dependencies. This allows us to build applications that are more readily testable, configurable, and scalable.
Flask application factory pattern (create_app()) vs global app instance: When you use the factory pattern, you enable isolated testing (pytest fixtures), environment-specific configurations, and clean blueprint registration without circular imports. The global app approach works for prototypes, but it becomes harder to maintain as applications grow beyond a few hundred lines of code. For Flask 3.0+ applications using blueprints, the factory pattern has become the standard approach.\n\nFlask has recommended the application factory pattern since version 0.9. It was introduced to facilitate testing with isolated app instances and to resolve circular import issues with extensions and blueprints in larger applications.
Global app Instance: Simple but Limited
Quickstart pattern (app.py):
If you’re starting a straightforward Flask project, you might write code like this:
from flask import Flask
app = Flask(__name__) # Global singleton
app.config['SECRET_KEY'] = 'dev'
app.config['DEBUG'] = True
@app.route('/')
def hello():
return 'Hello Global!'
if __name__ == '__main__':
app.run()
Pros:
- We can start with only 3 lines
- No boilerplate to configure
Cons (we hit these at around 100+ lines of code):
| Issue | Symptom | Impact |
|---|---|---|
| Testing | RuntimeError: No application found | No pytest support |
| Config | Hardcoded values | Production security risks |
| Extensions | Requires current_app workarounds | Circular import issues |
| Blueprints | Circular import errors | Difficult refactoring |
| Multiple envs | Duplicate files per environment | Increased maintenance |
Run: python app.py → Works for toy apps.
Application Factory Pattern: Production-Ready
Scalable structure (factory.py):
When you use the factory pattern, you create a function that builds your application on demand. This approach gives you much more control over configuration and dependencies:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(config_name='development'):
app = Flask(__name__)
app.config.from_object(f'config.{config_name.title()}Config')
# Init extensions post-config
db.init_app(app)
# Register blueprints
from .routes.main import main_bp
app.register_blueprint(main_bp)
return app
Configs (config/__init__.py):\n\nimport os\n\nYou can define different configuration classes for each environment:
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'default'
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
Usage:
We can then create our app with the appropriate configuration:
app = create_app('development')
if __name__ == '__main__':
app.run()
# CLI: flask --app factory:create_app run
Benefits:
- Testing: We can create isolated test instances:
app = create_app('testing'); with app.app_context(): ... - Environments: You can load different configs based on environment variables:
create_app(os.getenv('FLASK_ENV', 'production')) - Isolation: No globals means pytest fixtures stay clean.
Comparison: Factory vs Global
Before we dive deeper, let’s compare these approaches side by side:
| Aspect | Global app | Factory (create_app) | Winner |
|---|---|---|---|
| Prototypes | 10s setup (Good) | Boilerplate (Issue) | Global |
| Testing (pytest) | No context (Issue) | Clean fixtures (Good) | Factory |
| Config Mgmt | Hardcoded (Issue) | Classes/envs (Good) | Factory |
| Blueprints/Ext | Cycles (Issue) | Deferred init (Good) | Factory |
| Serverless/CI | Globals leak (Issue) | Per-instance (Good) | Factory |
| LOC Threshold | <200 | >200+ | Factory |
| Flask 3.0+ | Deprecated feel | Official docs | Factory |
The official Flask documentation now recommends the factory pattern for applications beyond basic prototypes.
When to Use Each?
You might wonder: when should I choose one approach over the other? The answer depends on your project’s scope and requirements.
- Global app: Best for tutorials, prototype applications with fewer than 5 routes, or applications without database connections or tests. You can get an MVP running in 5 minutes.
- Factory: Recommended for production applications. Use it when you have tests, databases, blueprints, or deployment requirements. We suggest migrating to the factory pattern once your application reaches around 50 lines of code.
Decision Tree:
Has tests/DB/blueprints? → Factory
Multi-env/deploy? → Factory
Solo prototype? → Global (but plan to migrate)
Migrating Global → Factory (5 Steps)
If you’re working with an existing Flask application that uses the global app pattern, you can migrate to the factory pattern in these five steps:
-
Extract config: Instead of hardcoding configuration values, move them to a config class:
# Before: app.config['DEBUG'] = True # After: app.config.from_object('config.DevelopmentConfig') -
Move globals to factory: Extensions like SQLAlchemy should be initialized outside the app, then configured inside the factory:
# extensions.py db = SQLAlchemy() # factory.py: db.init_app(app) -
Blueprint imports inside factory (breaks cycles): To avoid circular import errors, import blueprints inside the factory function rather than at the module level.
-
Update entrypoints: Your application’s entry point needs to call the factory function:
# wsgi.py from factory import create_app app = create_app() -
Pytest fixture: Update your test configuration to use the factory pattern:
import pytest from factory import create_app @pytest.fixture def app(): app = create_app('testing') with app.app_context(): yield app
You can verify the migration works by running pytest and flask run --app factory:create_app.
Production Deployment Checklist
Before deploying your factory-pattern Flask application to production, verify these items:
- Set
FLASK_APP=factory:create_appin your.envfile - Ensure all tests pass using
app_context() - Configure Gunicorn with:
gunicorn -w4 factory:create_app - Verify no circular imports exist (use pyreverse for auditing)
- Confirm distinct databases for dev, test, and production environments
The factory pattern promotes maintainable Flask applications that scale well as your project grows.
Related: 36. Flask CSP, 35. Flask vs FastAPI WS, 34. Flask Startup Opt, 33. Flask-SQLAlchemy
Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming