Why Flask Blueprints Aren't Enough for Large Apps ================================================== .. post:: 2026-05-28 :tags: flask, blueprints, architecture, best-practices :category: Engineering :author: Shopyo Team Flask blueprints are the recommended way to organize large Flask applications. They're a solid tool — but they're not a complete solution. As your application grows beyond a certain size, blueprints alone leave critical gaps that turn your codebase into technical debt. This post explains what blueprints do well, where they fall short, and what a complete modular architecture looks like. What Blueprints Do Well ----------------------- Blueprints solve the problem of route organization. Instead of one massive ``app.py`` with 500 route decorators, you split routes into logical groups: .. code-block:: python # auth/views.py auth_bp = Blueprint("auth", __name__) @auth_bp.route("/login") def login(): ... @auth_bp.route("/register") def register(): ... This is a meaningful improvement. It's the first step every Flask developer should take. But it's only the first step. Where Blueprints Fall Short --------------------------- **1. No model isolation** Blueprints only organize views. Your models still live in a single ``models.py`` or are scattered across files with no clear ownership. A module that owns its views should also own its models, forms, and templates. .. code-block:: text # What most blueprint-based apps look like: auth/ ├── views.py # routes models.py # all models, including auth models forms.py # all forms, including auth forms Auth models shouldn't live next to billing models any more than auth routes should live next to billing routes. **2. No template encapsulation** Blueprints can have a ``template_folder``, but there's no convention for how templates should be organized. Without enforced structure, templates end up in a flat directory with hundreds of files and unclear ownership. **3. No auto-discovery** Every blueprint must be manually imported and registered in your app factory: .. code-block:: python from auth.views import auth_bp from billing.views import billing_bp from dashboard.views import dashboard_bp # ... 20 more imports app.register_blueprint(auth_bp) app.register_blueprint(billing_bp) app.register_blueprint(dashboard_bp) # ... 20 more registrations Forget to import one? Your route silently doesn't exist. This is fragile and doesn't scale. **4. No enforcement of module boundaries** Nothing stops ``billing/views.py`` from importing ``auth/models.py`` directly. Over time, your modules become tightly coupled. You can't test, refactor, or remove a module without fear. **5. No built-in module assets** Serving static files for a module (CSS, JS, images) requires manual path configuration. There's no standard way to say "these assets belong to this module." The Modular Architecture: Blueprints + Boundaries ------------------------------------------------- The solution isn't to abandon blueprints — it's to add the missing layers around them. A true modular architecture adds: .. list-table:: :header-rows: 1 * - Concern - Blueprints Only - Modular Architecture * - Views - ✅ Organized - ✅ Organized * - Models - ❌ Global - ✅ Per-module * - Forms - ❌ Global - ✅ Per-module * - Templates - ❌ Flat - ✅ Per-module, namespaced * - Static assets - ❌ Manual - ✅ Per-module, auto-served * - Auto-discovery - ❌ Manual imports - ✅ Drop-in registration * - Boundary enforcement - ❌ None - ✅ Convention + tooling * - Tests - ❌ Centralized - ✅ Per-module How Shopyo Bridges the Gap -------------------------- Shopyo was built specifically to solve these problems. Every Shopyo module is a self-contained unit with its own views, models, forms, templates, tests, and static assets: .. code-block:: text modules/billing/ ├── __init__.py ├── view.py # routes (blueprint) ├── models.py # billing models only ├── forms.py # billing forms only ├── global.py # template variables, configs ├── info.json # module metadata ├── static/ # billing-specific CSS/JS ├── templates/ │ └── billing/ # namespaced templates └── tests/ ├── test_models.py └── test_views.py Modules are auto-discovered — you just drop them in the ``modules/`` folder. The blueprint is registered automatically. Static assets are served transparently in both development and production. Module boundaries are enforced by convention (no cross-module imports) and supported by the framework. This is what blueprints should have been from the start: a complete unit of functionality, not just a router with a name tag. Next Steps for Modular Flask ----------------------------- - :doc:`/usage` — Build your first modular Flask app - :doc:`/architecture` — How Shopyo's module system works - :doc:`/modules` — Creating and managing modules