Flask Framework Guide
Applies to: Flask 3.0+, REST APIs, Microservices, Web Applications Language Guide: @.claude/skills/python-guide/SKILL.md
Overview
Flask is a lightweight WSGI web framework providing the basics for building web applications while allowing flexibility in choosing components.
Use Flask when:
- •Building microservices or small-to-medium APIs
- •You want flexibility to choose your own ORM, auth, etc.
- •Rapid prototyping is needed
- •You prefer explicit over implicit behavior
Consider alternatives when:
- •You need async support (use FastAPI or Quart)
- •You want batteries-included (use Django)
- •High performance async is critical (use FastAPI)
Guardrails
Flask-Specific Guidelines
- •Use the application factory pattern (never use global app instances)
- •Use blueprints for modular routing
- •Use Flask extensions appropriately (init in extensions.py)
- •Configure via environment variables with class-based config
- •Use Marshmallow for validation/serialization
- •Implement proper error handlers for all error types
- •Use Flask-Migrate for database migrations
- •Use Flask-JWT-Extended for authentication
Security Guidelines
- •Never store plain passwords (use werkzeug.security)
- •Use environment variables for all secrets
- •Implement proper authentication and authorization
- •Validate all user input with Marshmallow schemas
- •Use HTTPS in production
- •Set secure cookie options (HTTPONLY, SECURE, SAMESITE)
- •Sanitize all database queries via SQLAlchemy ORM
- •Configure CORS restrictively in production
Testing Guidelines
- •Use pytest with Flask test client
- •Use fixtures for test data and app context
- •Test both success and error cases for every endpoint
- •Mock external services (never call real APIs in tests)
- •Use a separate test database
- •Coverage target: >80% for business logic
Project Structure
myproject/ ├── app/ │ ├── __init__.py # Application factory │ ├── config.py # Configuration classes │ ├── extensions.py # Flask extensions (single init point) │ ├── models/ │ │ ├── __init__.py │ │ ├── base.py # Base model class with mixins │ │ └── user.py │ ├── api/ │ │ ├── __init__.py # API blueprint registration │ │ ├── users.py # User endpoints │ │ └── auth.py # Auth endpoints │ ├── services/ │ │ ├── __init__.py │ │ └── user_service.py # Business logic (no Flask imports) │ ├── schemas/ │ │ ├── __init__.py │ │ └── user.py # Marshmallow schemas │ └── utils/ │ ├── __init__.py │ ├── errors.py # Custom exceptions and handlers │ └── decorators.py # Auth and role decorators ├── migrations/ # Alembic migrations (via Flask-Migrate) ├── tests/ │ ├── conftest.py # Fixtures: app, client, db_session │ ├── test_api/ │ │ └── test_users.py │ └── test_services/ │ └── test_user_service.py ├── .env.example ├── requirements.txt ├── requirements-dev.txt ├── pyproject.toml └── run.py # Entry point
Key conventions:
- •
app/__init__.pycontainscreate_app()factory only - •
app/extensions.pycentralizes all extension instances - •
app/services/holds business logic, no Flask imports - •
app/schemas/holds Marshmallow validation/serialization - •
app/utils/errors.pydefines custom exceptions and handlers
Application Factory
Always use the factory pattern. Never use a global app = Flask(__name__).
"""Flask application factory."""
from flask import Flask
from app.config import config
from app.extensions import db, migrate, ma, jwt, cors
def create_app(config_name: str = "development") -> Flask:
"""Create and configure the Flask application."""
app = Flask(__name__)
app.config.from_object(config[config_name])
register_extensions(app)
register_blueprints(app)
register_error_handlers(app)
register_commands(app)
return app
def register_extensions(app: Flask) -> None:
"""Initialize Flask extensions."""
db.init_app(app)
migrate.init_app(app, db)
ma.init_app(app)
jwt.init_app(app)
cors.init_app(app)
def register_blueprints(app: Flask) -> None:
"""Register Flask blueprints."""
from app.api import api_bp
app.register_blueprint(api_bp, url_prefix="/api/v1")
def register_error_handlers(app: Flask) -> None:
"""Register error handlers."""
from app.utils.errors import (
handle_app_error,
handle_validation_error,
handle_not_found,
handle_internal_error,
)
from marshmallow import ValidationError
app.register_error_handler(ValidationError, handle_validation_error)
app.register_error_handler(404, handle_not_found)
app.register_error_handler(500, handle_internal_error)
def register_commands(app: Flask) -> None:
"""Register CLI commands."""
from app.commands import seed_db
app.cli.add_command(seed_db)
Blueprints
Organize routes into blueprints. Register them in the factory.
"""API blueprint registration."""
from flask import Blueprint
api_bp = Blueprint("api", __name__)
# Import routes to register them
from app.api import users, auth # noqa: F401, E402
Blueprint endpoints follow REST conventions:
| Method | Route | Handler | Description |
|---|---|---|---|
| GET | /resources | get_resources() | List with pagination |
| GET | /resources/<id> | get_resource(id) | Get single resource |
| POST | /resources | create_resource() | Create resource |
| PATCH | /resources/<id> | update_resource(id) | Partial update |
| DELETE | /resources/<id> | delete_resource(id) | Delete resource |
Configuration
Use class-based configuration with environment variable overrides.
"""Application configuration."""
import os
from datetime import timedelta
from typing import Type
class Config:
"""Base configuration."""
SECRET_KEY = os.getenv("SECRET_KEY", "change-in-production")
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
"pool_pre_ping": True,
"pool_recycle": 300,
}
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", SECRET_KEY)
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30)
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*").split(",")
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv(
"DATABASE_URL",
"postgresql://postgres:postgres@localhost:5432/myapp_dev"
)
SQLALCHEMY_ECHO = True
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.getenv(
"TEST_DATABASE_URL",
"postgresql://postgres:postgres@localhost:5432/myapp_test"
)
class ProductionConfig(Config):
DEBUG = False
SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
config: dict[str, Type[Config]] = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
}
Extensions
Centralize all Flask extension instances in a single file.
"""Flask extensions initialization.""" from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_marshmallow import Marshmallow from flask_jwt_extended import JWTManager from flask_cors import CORS db = SQLAlchemy() migrate = Migrate() ma = Marshmallow() jwt = JWTManager() cors = CORS()
Common extensions and their purposes:
| Extension | Purpose |
|---|---|
| Flask-SQLAlchemy | ORM and database integration |
| Flask-Migrate | Alembic database migrations |
| Flask-Marshmallow | Serialization and validation |
| Flask-JWT-Extended | JWT authentication |
| Flask-CORS | Cross-origin resource sharing |
| Flask-Limiter | Rate limiting |
| Flask-Caching | Response and data caching |
| Flask-Mail | Email sending |
Error Handling
Define a custom exception hierarchy and register handlers.
"""Custom exceptions and error handlers."""
from flask import jsonify
class AppError(Exception):
"""Base application error."""
def __init__(self, message: str, status_code: int = 400):
super().__init__(message)
self.message = message
self.status_code = status_code
class NotFoundError(AppError):
def __init__(self, message: str = "Resource not found"):
super().__init__(message, status_code=404)
class UnauthorizedError(AppError):
def __init__(self, message: str = "Unauthorized"):
super().__init__(message, status_code=401)
class ForbiddenError(AppError):
def __init__(self, message: str = "Forbidden"):
super().__init__(message, status_code=403)
class ConflictError(AppError):
def __init__(self, message: str = "Conflict"):
super().__init__(message, status_code=409)
def handle_validation_error(error):
return jsonify({"error": "Validation error", "details": error.messages}), 400
def handle_app_error(error: AppError):
return jsonify({"error": error.message}), error.status_code
def handle_not_found(error):
return jsonify({"error": "Not found"}), 404
def handle_internal_error(error):
return jsonify({"error": "Internal server error"}), 500
Jinja2 Templates
When building server-rendered pages (not just APIs):
- •Templates live in
app/templates/with abase.htmllayout - •Use template inheritance:
{% extends "base.html" %} - •Use
{% block content %}for page-specific content - •Escape user content automatically (Jinja2 autoescape is on by default)
- •Use
url_for()for all URLs in templates - •Keep logic out of templates; use filters and context processors
{# app/templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My App{% endblock %}</title>
</head>
<body>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
CLI Commands
Register custom CLI commands via Click.
"""Custom CLI commands."""
import click
from flask.cli import with_appcontext
from app.extensions import db
@click.command("seed-db")
@with_appcontext
def seed_db():
"""Seed database with initial data."""
# Create initial records
db.session.commit()
click.echo("Database seeded.")
Commands Reference
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Set environment variables
export FLASK_APP=run.py
export FLASK_ENV=development
# Initialize database
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
# Seed database
flask seed-db
# Run development server
flask run
# Run with Gunicorn (production)
gunicorn -w 4 -b 0.0.0.0:5000 "app:create_app('production')"
# Run tests
pytest
pytest -v --cov=app --cov-report=html
# Lint and format
black .
isort .
ruff check .
mypy app/
Dependencies
requirements.txt
Flask>=3.0.0 Flask-SQLAlchemy>=3.1.0 Flask-Migrate>=4.0.0 Flask-Marshmallow>=0.15.0 Flask-JWT-Extended>=4.6.0 Flask-CORS>=4.0.0 marshmallow-sqlalchemy>=0.29.0 psycopg2-binary>=2.9.9 python-dotenv>=1.0.0 gunicorn>=21.0.0
requirements-dev.txt
-r requirements.txt pytest>=7.4.0 pytest-flask>=1.2.0 pytest-cov>=4.1.0 black>=23.0.0 isort>=5.12.0 mypy>=1.5.0 ruff>=0.1.0
Advanced Topics
For detailed code examples and advanced patterns, see:
- •references/patterns.md -- Models, schemas, services, API endpoints, authentication, decorators, testing, and deployment patterns