Flask API Fundamentals · Structure and Error Handling

Blueprints for API Modules

Split a growing Flask API into blueprints, register them in an app factory with URL prefixes, and avoid circular imports.

Create a free account to save this page, take notes and track your progress. Sign in

A Flask app can start as one file, but by the time it has users, orders and billing endpoints that file is hundreds of lines long and everyone edits it at once. Blueprints let you split routes into modules that each own one area of the API, then plug them into the app with a URL prefix.

A blueprint is a group of routes

You create a Blueprint the same way you create an app, and add routes to it with the same decorators:

# app/users.py
from flask import Blueprint, jsonify

users = Blueprint("users", __name__, url_prefix="/users")
USERS = {1: "Ada", 2: "Linus"}


@users.get("/")
def list_users():
    return jsonify([{"id": i, "name": n} for i, n in USERS.items()])


@users.get("/<int:user_id>")
def get_user(user_id):
    if user_id not in USERS:
        return jsonify(error="user not found"), 404
    return jsonify(id=user_id, name=USERS[user_id])

Nothing is served yet. A blueprint only records its routes; they become real when you register it on an app.

Register blueprints in an app factory

The usual pattern is a create_app() function that builds the app, loads its settings and registers every blueprint. Tests can then create a fresh app for each run.

# app/__init__.py
from flask import Flask


def create_app(config=None):
    app = Flask(__name__)
    app.config.update(config or {})

    from .users import users
    from .orders import orders
    app.register_blueprint(users, url_prefix="/api/v1/users")
    app.register_blueprint(orders, url_prefix="/api/v1/orders")
    return app

A url_prefix passed to register_blueprint replaces the one set on the blueprint. That makes it easy to mount the same module under /api/v1 today and /api/v2 later.

The final URL is the prefix plus the route. You can see the rule without running Flask:

How prefixes build the final URLsTry it yourself
def mount(prefix, routes):
    return [(method, prefix.rstrip("/") + path) for method, path in routes]


users = [("GET", "/"), ("GET", "/<int:user_id>"), ("POST", "/")]
orders = [("GET", "/"), ("POST", "/<int:order_id>/cancel")]

table = mount("/api/v1/users", users) + mount("/api/v1/orders", orders)
for method, url in table:
    print(f"{method:5} {url}")

Output

GET   /api/v1/users/
GET   /api/v1/users/<int:user_id>
POST  /api/v1/users/
GET   /api/v1/orders/
POST  /api/v1/orders/<int:order_id>/cancel

Endpoint names include the blueprint

Inside a blueprint, each route's endpoint name is prefixed with the blueprint's name. Use that name with url_for, for example to set a Location header after creating something:

from flask import url_for


@users.post("/")
def create_user():
    new_id = 3
    body = jsonify(id=new_id)
    return body, 201, {"Location": url_for("users.get_user", user_id=new_id)}

Inside the same blueprint you can also write the short form url_for(".get_user", ...).

Hooks and error handlers per blueprint

A blueprint can have its own before_request hook and error handlers. They run only for that blueprint's routes, which is handy when, say, only the admin API needs an extra check:

from flask import Blueprint, abort, request

admin = Blueprint("admin", __name__)


@admin.before_request
def require_admin_key():
    if request.headers.get("X-Admin-Key") != "change-me":
        abort(403)

One exception: a blueprint's 404 and 405 handlers don't catch URLs that match no route at all, because Flask can't tell which blueprint such a URL belongs to. Register those handlers on the app (see the next lesson).

How to split an API

Group routes by the thing they manage, not by HTTP method or by file type:

app/
  __init__.py      create_app() registers the blueprints
  users.py         /api/v1/users
  orders.py        /api/v1/orders
  billing.py       /api/v1/billing
  errors.py        app-wide JSON error handlers

Common mistakes

  • Two blueprints with the same name. Blueprint names must be unique within an app, because they prefix endpoint names. Flask raises an error when a second blueprint reuses a name.
  • Circular imports. If users.py imports app from app/__init__.py and that file imports users.py, Python fails. Import the blueprints inside create_app(), as above, and use current_app inside handlers instead of a global app.
  • Forgetting to register. A route defined on a blueprint that was never registered returns 404. Run flask routes to list every URL the app really serves.

Try it

Create an orders blueprint with GET / and GET /<int:order_id>, register it under /api/v1/orders, and check with flask routes that both URLs appear.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?