Learn / Frameworks / Flask / Blueprints, Testing and Deployment

Intermediate 16 min

Blueprints, Testing and Deployment

Structure larger apps, test them and run with gunicorn.

What you will learn

  • Use blueprints and the app factory
  • Write pytest tests
  • Deploy with gunicorn

A single app.py is fine for learning but becomes unmanageable as an app grows. Flask has patterns for organizing code, plus a standard path to testing and production.

Blueprints

A blueprint groups related routes, templates and static files into a module.

# myapp/blog/routes.py
from flask import Blueprint, jsonify

bp = Blueprint("blog", __name__, url_prefix="/blog")

@bp.route("/")
def index():
    return jsonify(posts=[])

@bp.route("/<int:post_id>")
def show(post_id):
    return jsonify(id=post_id)

The application factory

Create the app inside a function, so you can build differently configured instances, for example one for tests and one for production.

# myapp/__init__.py
from flask import Flask
from .extensions import db

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_mapping(
        SECRET_KEY="dev",
        SQLALCHEMY_DATABASE_URI="sqlite:///app.db",
    )
    if config:
        app.config.update(config)
    app.config.from_prefixed_env()        # FLASK_SECRET_KEY etc. override

    db.init_app(app)

    from .blog.routes import bp as blog_bp
    app.register_blueprint(blog_bp)
    return app
myapp/
  __init__.py        # create_app
  extensions.py      # db = SQLAlchemy()
  models.py
  blog/
    __init__.py
    routes.py
    templates/
tests/
  test_blog.py
wsgi.py
# tests/test_blog.py
import pytest
from myapp import create_app
from myapp.extensions import db

@pytest.fixture
def client():
    app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite:///:memory:"})
    with app.app_context():
        db.create_all()
        yield app.test_client()

def test_index_returns_json(client):
    res = client.get("/blog/")
    assert res.status_code == 200
    assert res.get_json() == {"posts": []}

def test_unknown_route_404(client):
    assert client.get("/nope").status_code == 404
pip install pytest
pytest -q

Running in production

The built-in dev server is not for production. Use a WSGI server such as gunicorn, usually behind Nginx or a platform's load balancer.

# wsgi.py
from myapp import create_app
app = create_app()
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]

Production checklist

  • DEBUG off; a strong SECRET_KEY read from an environment variable.
  • A real database (PostgreSQL) with migrations applied.
  • HTTPS, secure cookies (SESSION_COOKIE_SECURE) and CSRF protection on forms.
  • Structured logging and error monitoring (for example Sentry).
Next steps

Add Flask-Login for user sessions, Flask-WTF for forms, and Flask-Smorest or Flask-RESTX if you want OpenAPI docs for a JSON API.

Try it yourself

Split a small app into an app factory plus an api blueprint at /api with a /ping route, and write a pytest test that checks it returns 200.

Show solution
# api.py
bp = Blueprint("api", __name__, url_prefix="/api")
@bp.route("/ping")
def ping():
    return {"pong": True}

# factory: app.register_blueprint(bp)
# test
def test_ping(client):
    assert client.get("/api/ping").status_code == 200