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 appmyapp/
__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 == 404pip install pytest
pytest -qRunning 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:appFROM 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
DEBUGoff; a strongSECRET_KEYread 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).
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