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.py contains create_app() factory only
-
app/extensions.py centralizes all extension instances
-
app/services/ holds business logic, no Flask imports
-
app/schemas/ holds Marshmallow validation/serialization
-
app/utils/errors.py defines 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 a base.html layout
-
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
External References
-
Flask Documentation
-
Flask-SQLAlchemy
-
Flask-Migrate
-
Flask-JWT-Extended
-
Marshmallow
-
SQLAlchemy