Flask Project Structure: Blueprints, Templates, and Config

• Python • By TryzTech Team
FlaskPythonBackendWeb Development

One app.py file feels practical while it contains only a handful of routes. The problem arrives gradually: page routes become mixed with login, config, models, and API endpoints. A file that was once easy to understand starts requiring a long scroll just to find one function.

Blueprints group routes by application area. A Blueprint is not a second Flask application and does not run on its own. It collects routes and is then registered with the main application object.

A Small Structure That Stays Readable

We will separate the notes routes without introducing too many layers:

# File: folder-structure.txt
flask-notes/
├── app.py
├── config.py
├── routes/
│   ├── __init__.py
│   └── notes.py
├── templates/
│   └── notes/
│       └── index.html
└── static/
    └── css/
        └── app.css

__init__.py may be empty. Its presence clearly marks routes as a Python package, allowing routes.notes to be imported.

Create a Notes Blueprint

Place every note-related route in routes/notes.py:

# File: routes/notes.py
from flask import Blueprint, render_template

notes_bp = Blueprint("notes", __name__, url_prefix="/notes")


@notes_bp.get("")
def index():
    notes = ["Meeting notes", "Book ideas"]
    return render_template("notes/index.html", notes=notes)

The first argument, "notes", is the Blueprint name. It becomes an endpoint namespace. Because the route function is named index, its full endpoint is notes.index. Templates can later refer to it with url_for("notes.index").

The url_prefix="/notes" value adds the same prefix to every route in this Blueprint. The empty path in @notes_bp.get("") becomes /notes. A future /<int:note_id> route becomes /notes/<int:note_id>.

Assemble the App with create_app()

The main file creates Flask and registers the Blueprint:

# File: app.py
from flask import Flask
from routes.notes import notes_bp


def create_app():
    app = Flask(__name__)
    app.config.from_object("config.Config")
    app.register_blueprint(notes_bp)
    return app

Basic configuration can live in its own file:

# File: config.py
class Config:
    SECRET_KEY = "dev-secret"

The create_app() function is called an application factory because it creates and returns an application instance. This pattern is useful when tests need different configuration or when several app instances must exist in one process.

Flask can discover the factory automatically:

# File: terminal
python -m flask --app app run --debug

Because the endpoint is namespaced under notes, links use notes.index:

<!-- File: templates/notes/index.html -->
<h1>Notes</h1>

<ul>
  {% for note in notes %}
    <li>{{ note }}</li>
  {% endfor %}
</ul>

<a href="{{ url_for('notes.index') }}">Reload notes</a>

The namespace prevents collisions when another Blueprint also has a function named index. Authentication can expose auth.index while notes keeps notes.index.

Do Not Split the Project Too Early

Blueprints solve route grouping, but that does not mean every function deserves a separate file. Too many folders in a small project can make the flow harder to follow. Split code after clear groups emerge, such as notes, auth, and api.

Watch for circular imports as well. Routes may import models or extensions, but they should avoid importing the app object from the main file. A database extension can be created separately and attached through init_app() inside the factory.

As an exercise, add routes/auth.py with an auth Blueprint, a /login route, and its own template. After registering it in create_app(), inspect the route table with:

# File: terminal
python -m flask --app app routes

The output lists registered endpoints, methods, and URLs. It is a quick way to find a Blueprint that was never registered or a prefix that differs from what you expected.

Keep reading within the same topic.

Don't Miss Out

Get the latest tech articles, tips, and insights delivered to your inbox.