Skills › Software Development › Backend & APIs
flask
**Status**: Production Ready
Tools: flask,config,flask_sqlalchemy,flask_login,dotenv,app,flask_wtf,wtforms
The full skill
—
name: flask
description: |
Build Python web applications with Flask, using the application factory pattern, Blueprints,
and Flask-SQLAlchemy. Covers project structure, authentication, and configuration management.
Use when: creating Flask projects, organizing with blueprints, implementing authentication,
or troubleshooting circular imports, application context errors, or blueprint registration.
—
# Flask Skill
Production-tested patterns for Flask with the application factory pattern, Blueprints, and Flask-SQLAlchemy.
**Latest Versions** (verified December 2025):
– Flask: 3.1.2
– Flask-SQLAlchemy: 3.1.1
– Flask-Login: 0.6.3
– Flask-WTF: 1.2.2
– Werkzeug: 3.1.3
—
## Quick Start
### Project Setup with uv
“`bash
# Create project
uv init my-flask-app
cd my-flask-app
# Add dependencies
uv add flask flask-sqlalchemy flask-login flask-wtf python-dotenv
# Run development server
uv run flask –app app run –debug
“`
### Minimal Working Example
“`python
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return {"message": "Hello, World!"}
if __name__ == "__main__":
app.run(debug=True)
“`
Run: `uv run flask –app app run –debug`
—
## Project Structure (Application Factory)
For maintainable applications, use the factory pattern with blueprints:
“`
my-flask-app/
âââ pyproject.toml
âââ config.py # Configuration classes
âââ run.py # Entry point
â
âââ app/
â âââ __init__.py # Application factory (create_app)
â âââ extensions.py # Flask extensions (db, login_manager)
â âââ models.py # SQLAlchemy models
â â
â âââ main/ # Main blueprint
â â âââ __init__.py
â â âââ routes.py
â â
â âââ auth/ # Auth blueprint
â â âââ __init__.py
â â âââ routes.py
â â âââ forms.py
â â
â âââ templates/
â â âââ base.html
â â âââ main/
â â âââ auth/
â â
â âââ static/
â âââ css/
â âââ js/
â
âââ tests/
âââ conftest.py
âââ test_main.py
“`
—
## Core Patterns
### Application Factory
“`python
# app/__init__.py
from flask import Flask
from app.extensions import db, login_manager
from config import Config
def create_app(config_class=Config):
"""Application factory function."""
app = Flask(__name__)
app.config.from_object(config_class)
# Initialize extensions
db.init_app(app)
login_manager.init_app(app)
# Register blueprints
from app.main import bp as main_bp
from app.auth import bp as auth_bp
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp, url_prefix="/auth")
# Create database tables
with app.app_context():
db.create_all()
return app
“`
**Key Benefits**:
– Multiple app instances with different configs (testing)
– Avoids circular imports
– Extensions initialized once, bound to app later
### Extensions Module
“`python
# app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = "auth.login"
login_manager.login_message_category = "info"
“`
**Why separate file?**: Prevents circular imports – models can import `db` without importing `app`.
### Configuration
“`python
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key")
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
"""Production configuration."""
DEBUG = False
“`
### Entry Point
“`python
# run.py
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
“`
Run: `flask –app run run –debug`
—
## Blueprints
### Creating a Blueprint
“`python
# app/main/__init__.py
from flask import Blueprint
bp = Blueprint("main", __name__)
from app.main import routes # Import routes after bp is created!
“`
“`python
# app/main/routes.py
from flask import render_template, jsonify
from app.main import bp
@bp.route("/")
def index():
return render_template("main/index.html")
@bp.route("/api/health")
def health():
return jsonify({"status": "ok"})
“`
### Blueprint with Templates
“`python
# app/auth/__init__.py
from flask import Blueprint
bp = Blueprint(
"auth",
__name__,
template_folder="templates", # Blueprint-specific templates
static_folder="static", # Blueprint-specific static files
)
from app.auth import routes
“`
—
## Database Models
“`python
# app/models.py
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app.extensions import db, login_manager
class User(UserMixin, db.Model):
"""User model for authentication."""
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(256), nullable=False)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"<User {self.email}>"
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
“`
—
## Authentication with Flask-Login
### Auth Forms
“`python
# app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
from app.models import User
class LoginForm(FlaskForm):
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
remember = BooleanField("Remember Me")
submit = SubmitField("Login")
class RegistrationForm(FlaskForm):
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired(), Length(min=8)])
confirm = PasswordField("Confirm Password", validators=[
DataRequired(), EqualTo("password", message="Passwords must match")
])
submit = SubmitField("Register")
def validate_email(self, field):
if User.query.filter_by(email=field.data).first():
raise ValidationError("Email already registered.")
“`
### Auth Routes
“`python
# app/auth/routes.py
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm
from app.extensions import db
from app.models import User
@bp.route("/register", methods=["GET", "POSTf:T48d,"""Application configuration classes."""
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-secret-key-change-in-production")
SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_ECHO = True
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
"""Production configuration."""
DEBUG = False
@classmethod
def init_app(cls, app):
"""Production-specific initialization."""
# Ensure SECRET_KEY is set
assert cls.SECRET_KEY != "dev-secret-key-change-in-production", \
"Set SECRET_KEY environment variable for production!"
# Config mapping
config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
"default": DevelopmentConfig,
}
20:T4d2,"""Authentication forms."""
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
from app.models import User
class LoginForm(FlaskForm):
"""User login form."""
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
remember = BooleanField("Remember Me")
submit = SubmitField("Log In")
class RegistrationForm(FlaskForm):
"""User registration form."""
email = StringField("Email", validators=[DataRequired(), Email()])
password = PasswordField(
"Password",
validators=[DataRequired(), Length(min=8, message="Password must be at least 8 characters")]
)
confirm = PasswordField(
"Confirm Password",
validators=[DataRequired(), EqualTo("password", message="Passwords must match")]
)
submit = SubmitField("Register")
def validate_email(self, field):
"""Check if email is already registered."""
if User.query.filter_by(email=field.data).first():
raise ValidationError("This email is already registered.")
21:T471,"""Database models."""
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app.extensions import db, login_manager
class User(UserMixin, db.Model):
"""User model for authentication."""
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(256), nullable=False)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def set_password(self, password):
"""Hash and set the user's password."""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Check if the provided password matches the hash."""
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"<User {self.email}>"
@login_manager.user_loader
def load_user(user_id):
"""Load user by ID for Flask-Login."""
return User.query.get(int(user_id))
22:T6d3,"""Authentication routes."""
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from app.auth import bp
from app.auth.forms import LoginForm, RegistrationForm
from app.extensions import db
from app.models import User
@bp.route("/register", methods=["GET", "POST:T4b9,"""Tests for authentication routes."""
def test_register_page(client):
"""Test registration page loads."""
response = client.get("/auth/register")
assert response.status_code == 200
def test_login_page(client):
"""Test login page loads."""
response = client.get("/auth/login")
assert response.status_code == 200
def test_register_user(client):
"""Test user registration."""
response = client.post("/auth/register", data={
"email": "[email protected]",
"password": "testpass123",
"confirm": "testpass123",
}, follow_redirects=True)
assert response.status_code == 200
def test_login_user(client, user):
"""Test user login."""
response = client.post("/auth/login", data={
"email": "[email protected]",
"password": "testpassword123",
}, follow_redirects=True)
assert response.status_code == 200
def test_login_invalid_password(client, user):
"""Test login with wrong password."""
response = client.post("/auth/login", data={
"email": "[email protected]",
"password": "wrongpassword",
})
assert response.status_code == 200
assert b"Invalid email or password" in response.data
9:[["$","$L1b",null,{"locale":"en","skill":{"id":"jezweb-claude-skills-skills-flask-skill-md","name":"flask","author":"jezweb","authorAvatarUrl":"https://avatars.githubusercontent.com/u/13249054?v=4"}}],["$","$L1c",null,{"locale":"en","skill":{"id":"jezweb-claude-skills-skills-flask-skill-md","name":"flask","author":"jezweb","authorAvatar":"https://avatars.githubusercontent.com/u/13249054?v=4","description":"**Status**: Production Ready","githubUrl":"https://github.com/jezweb/claude-skills/tree/main/skills/flask","skillContent":null,"hasMarketplaceJson":true},"files":[{"id":"9643a706-e253-429f-8de5-f14a8a35cebb","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":".claude-plugin","name":".claude-plugin","type":"directory","size":null,"content":null,"isBinary":false},{"id":"99f32cff-693f-483e-9a69-b5282f7b2297","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app","name":"app","type":"directory","size":null,"content":null,"isBinary":false},{"id":"b5d05c20-fe16-4b8c-8521-e84f5101dc33","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app/auth","name":"auth","type":"directory","size":null,"content":null,"isBinary":false},{"id":"4a402086-b3fc-4400-b889-c36b9031daf8","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates/app/main","name":"main","type":"directory","size":null,"content":null,"isBinary":false},{"id":"5cafa951-633d-4b56-a63e-afaa2db32d4c","skillId":"jezweb-claude-skills-skills-flask-skill-md","path":"templates","name":"templates","type":"directory","size":null,"content":null,"isBinary":false},{"id":"9cb3b2ef-9ffa-493c-88da-9c13