The go-to resource for upgrading Python, Django, Flask, and your dependencies.

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):

IssueSymptomImpact
TestingRuntimeError: No application foundNo pytest support
ConfigHardcoded valuesProduction security risks
ExtensionsRequires current_app workaroundsCircular import issues
BlueprintsCircular import errorsDifficult refactoring
Multiple envsDuplicate files per environmentIncreased 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:

AspectGlobal appFactory (create_app)Winner
Prototypes10s setup (Good)Boilerplate (Issue)Global
Testing (pytest)No context (Issue)Clean fixtures (Good)Factory
Config MgmtHardcoded (Issue)Classes/envs (Good)Factory
Blueprints/ExtCycles (Issue)Deferred init (Good)Factory
Serverless/CIGlobals leak (Issue)Per-instance (Good)Factory
LOC Threshold<200>200+Factory
Flask 3.0+Deprecated feelOfficial docsFactory

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:

  1. 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')
  2. 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)
  3. Blueprint imports inside factory (breaks cycles): To avoid circular import errors, import blueprints inside the factory function rather than at the module level.

  4. Update entrypoints: Your application’s entry point needs to call the factory function:

    # wsgi.py
    from factory import create_app
    app = create_app()
  5. 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_app in your .env file
  • 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