Flask Essentials

Flask web framework essentials for building web applications and APIs.


Installation

1pip install flask
2
3# With extensions
4pip install flask flask-sqlalchemy flask-migrate flask-login flask-wtf flask-cors

Basic Flask App

 1from flask import Flask
 2
 3app = Flask(__name__)
 4
 5@app.route('/')
 6def hello():
 7    return 'Hello, World!'
 8
 9@app.route('/user/<username>')
10def show_user(username):
11    return f'User: {username}'
12
13@app.route('/post/<int:post_id>')
14def show_post(post_id):
15    return f'Post ID: {post_id}'
16
17if __name__ == '__main__':
18    app.run(debug=True)

Request and Response

 1from flask import Flask, request, jsonify, make_response
 2
 3app = Flask(__name__)
 4
 5@app.route('/data', methods=['GET', 'POST'])
 6def handle_data():
 7    if request.method == 'POST':
 8        # Get JSON data
 9        data = request.get_json()
10        
11        # Get form data
12        name = request.form.get('name')
13        
14        # Get query parameters
15        page = request.args.get('page', 1, type=int)
16        
17        # Get headers
18        auth = request.headers.get('Authorization')
19        
20        return jsonify({'message': 'Data received', 'data': data})
21    
22    return jsonify({'message': 'Send POST request'})
23
24@app.route('/custom-response')
25def custom_response():
26    response = make_response(jsonify({'status': 'ok'}), 200)
27    response.headers['X-Custom-Header'] = 'value'
28    return response
29
30@app.route('/download')
31def download_file():
32    from flask import send_file
33    return send_file('path/to/file.pdf', as_attachment=True)

Templates (Jinja2)

 1from flask import Flask, render_template
 2
 3app = Flask(__name__)
 4
 5@app.route('/hello/<name>')
 6def hello(name):
 7    return render_template('hello.html', name=name)
 8
 9@app.route('/users')
10def users():
11    users = [
12        {'id': 1, 'name': 'Alice'},
13        {'id': 2, 'name': 'Bob'}
14    ]
15    return render_template('users.html', users=users)
 1<!-- templates/hello.html -->
 2<!DOCTYPE html>
 3<html>
 4<head>
 5    <title>Hello</title>
 6</head>
 7<body>
 8    <h1>Hello, {{ name }}!</h1>
 9    {% if name == 'Admin' %}
10        <p>Welcome, administrator!</p>
11    {% endif %}
12</body>
13</html>
 1<!-- templates/users.html -->
 2<!DOCTYPE html>
 3<html>
 4<head>
 5    <title>Users</title>
 6</head>
 7<body>
 8    <h1>Users</h1>
 9    <ul>
10    {% for user in users %}
11        <li>{{ user.name }} (ID: {{ user.id }})</li>
12    {% endfor %}
13    </ul>
14</body>
15</html>

Blueprints (Modular Apps)

 1# app.py
 2from flask import Flask
 3from blueprints.auth import auth_bp
 4from blueprints.api import api_bp
 5
 6app = Flask(__name__)
 7
 8app.register_blueprint(auth_bp, url_prefix='/auth')
 9app.register_blueprint(api_bp, url_prefix='/api')
10
11if __name__ == '__main__':
12    app.run(debug=True)
 1# blueprints/auth.py
 2from flask import Blueprint, request, jsonify
 3
 4auth_bp = Blueprint('auth', __name__)
 5
 6@auth_bp.route('/login', methods=['POST'])
 7def login():
 8    data = request.get_json()
 9    username = data.get('username')
10    password = data.get('password')
11    
12    # Authenticate user
13    if username == 'admin' and password == 'secret':
14        return jsonify({'token': 'abc123'})
15    
16    return jsonify({'error': 'Invalid credentials'}), 401
17
18@auth_bp.route('/logout', methods=['POST'])
19def logout():
20    return jsonify({'message': 'Logged out'})
 1# blueprints/api.py
 2from flask import Blueprint, jsonify
 3
 4api_bp = Blueprint('api', __name__)
 5
 6@api_bp.route('/users')
 7def get_users():
 8    users = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
 9    return jsonify(users)
10
11@api_bp.route('/users/<int:user_id>')
12def get_user(user_id):
13    user = {'id': user_id, 'name': 'Alice'}
14    return jsonify(user)

Database (Flask-SQLAlchemy)

 1from flask import Flask, jsonify
 2from flask_sqlalchemy import SQLAlchemy
 3from datetime import datetime
 4
 5app = Flask(__name__)
 6app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
 7app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
 8
 9db = SQLAlchemy(app)
10
11class User(db.Model):
12    id = db.Column(db.Integer, primary_key=True)
13    username = db.Column(db.String(80), unique=True, nullable=False)
14    email = db.Column(db.String(120), unique=True, nullable=False)
15    created_at = db.Column(db.DateTime, default=datetime.utcnow)
16    posts = db.relationship('Post', backref='author', lazy=True)
17    
18    def to_dict(self):
19        return {
20            'id': self.id,
21            'username': self.username,
22            'email': self.email,
23            'created_at': self.created_at.isoformat()
24        }
25
26class Post(db.Model):
27    id = db.Column(db.Integer, primary_key=True)
28    title = db.Column(db.String(200), nullable=False)
29    content = db.Column(db.Text, nullable=False)
30    created_at = db.Column(db.DateTime, default=datetime.utcnow)
31    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
32    
33    def to_dict(self):
34        return {
35            'id': self.id,
36            'title': self.title,
37            'content': self.content,
38            'created_at': self.created_at.isoformat(),
39            'author': self.author.username
40        }
41
42# Create tables
43with app.app_context():
44    db.create_all()
45
46@app.route('/users', methods=['GET', 'POST'])
47def users():
48    if request.method == 'POST':
49        data = request.get_json()
50        user = User(username=data['username'], email=data['email'])
51        db.session.add(user)
52        db.session.commit()
53        return jsonify(user.to_dict()), 201
54    
55    users = User.query.all()
56    return jsonify([user.to_dict() for user in users])
57
58@app.route('/users/<int:user_id>')
59def get_user(user_id):
60    user = User.query.get_or_404(user_id)
61    return jsonify(user.to_dict())
62
63@app.route('/posts', methods=['GET', 'POST'])
64def posts():
65    if request.method == 'POST':
66        data = request.get_json()
67        post = Post(
68            title=data['title'],
69            content=data['content'],
70            user_id=data['user_id']
71        )
72        db.session.add(post)
73        db.session.commit()
74        return jsonify(post.to_dict()), 201
75    
76    posts = Post.query.all()
77    return jsonify([post.to_dict() for post in posts])

Database Migrations (Flask-Migrate)

 1from flask import Flask
 2from flask_sqlalchemy import SQLAlchemy
 3from flask_migrate import Migrate
 4
 5app = Flask(__name__)
 6app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
 7
 8db = SQLAlchemy(app)
 9migrate = Migrate(app, db)
10
11class User(db.Model):
12    id = db.Column(db.Integer, primary_key=True)
13    username = db.Column(db.String(80), nullable=False)
 1# Initialize migrations
 2flask db init
 3
 4# Create migration
 5flask db migrate -m "Initial migration"
 6
 7# Apply migration
 8flask db upgrade
 9
10# Rollback
11flask db downgrade
12
13# Show current version
14flask db current
15
16# Show migration history
17flask db history

Authentication (Flask-Login)

 1from flask import Flask, request, jsonify
 2from flask_sqlalchemy import SQLAlchemy
 3from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
 4from werkzeug.security import generate_password_hash, check_password_hash
 5
 6app = Flask(__name__)
 7app.config['SECRET_KEY'] = 'your-secret-key'
 8app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
 9
10db = SQLAlchemy(app)
11login_manager = LoginManager(app)
12
13class User(UserMixin, db.Model):
14    id = db.Column(db.Integer, primary_key=True)
15    username = db.Column(db.String(80), unique=True, nullable=False)
16    password_hash = db.Column(db.String(128))
17    
18    def set_password(self, password):
19        self.password_hash = generate_password_hash(password)
20    
21    def check_password(self, password):
22        return check_password_hash(self.password_hash, password)
23
24@login_manager.user_loader
25def load_user(user_id):
26    return User.query.get(int(user_id))
27
28@app.route('/register', methods=['POST'])
29def register():
30    data = request.get_json()
31    user = User(username=data['username'])
32    user.set_password(data['password'])
33    db.session.add(user)
34    db.session.commit()
35    return jsonify({'message': 'User created'}), 201
36
37@app.route('/login', methods=['POST'])
38def login():
39    data = request.get_json()
40    user = User.query.filter_by(username=data['username']).first()
41    
42    if user and user.check_password(data['password']):
43        login_user(user)
44        return jsonify({'message': 'Logged in'})
45    
46    return jsonify({'error': 'Invalid credentials'}), 401
47
48@app.route('/logout')
49@login_required
50def logout():
51    logout_user()
52    return jsonify({'message': 'Logged out'})
53
54@app.route('/profile')
55@login_required
56def profile():
57    return jsonify({
58        'id': current_user.id,
59        'username': current_user.username
60    })

Forms (Flask-WTF)

 1from flask import Flask, render_template, redirect, url_for, flash
 2from flask_wtf import FlaskForm
 3from wtforms import StringField, PasswordField, SubmitField
 4from wtforms.validators import DataRequired, Email, Length
 5
 6app = Flask(__name__)
 7app.config['SECRET_KEY'] = 'your-secret-key'
 8
 9class LoginForm(FlaskForm):
10    username = StringField('Username', validators=[DataRequired(), Length(min=3, max=80)])
11    password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
12    submit = SubmitField('Login')
13
14class RegistrationForm(FlaskForm):
15    username = StringField('Username', validators=[DataRequired(), Length(min=3, max=80)])
16    email = StringField('Email', validators=[DataRequired(), Email()])
17    password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
18    submit = SubmitField('Register')
19
20@app.route('/login', methods=['GET', 'POST'])
21def login():
22    form = LoginForm()
23    if form.validate_on_submit():
24        username = form.username.data
25        password = form.password.data
26        # Authenticate user
27        flash('Login successful!', 'success')
28        return redirect(url_for('index'))
29    return render_template('login.html', form=form)
30
31@app.route('/register', methods=['GET', 'POST'])
32def register():
33    form = RegistrationForm()
34    if form.validate_on_submit():
35        # Create user
36        flash('Registration successful!', 'success')
37        return redirect(url_for('login'))
38    return render_template('register.html', form=form)
 1<!-- templates/login.html -->
 2<!DOCTYPE html>
 3<html>
 4<head>
 5    <title>Login</title>
 6</head>
 7<body>
 8    <h1>Login</h1>
 9    {% with messages = get_flashed_messages(with_categories=true) %}
10        {% if messages %}
11            {% for category, message in messages %}
12                <div class="alert alert-{{ category }}">{{ message }}</div>
13            {% endfor %}
14        {% endif %}
15    {% endwith %}
16    
17    <form method="POST">
18        {{ form.hidden_tag() }}
19        <div>
20            {{ form.username.label }}
21            {{ form.username(size=32) }}
22            {% if form.username.errors %}
23                <span style="color: red;">{{ form.username.errors[0] }}</span>
24            {% endif %}
25        </div>
26        <div>
27            {{ form.password.label }}
28            {{ form.password(size=32) }}
29            {% if form.password.errors %}
30                <span style="color: red;">{{ form.password.errors[0] }}</span>
31            {% endif %}
32        </div>
33        <div>
34            {{ form.submit() }}
35        </div>
36    </form>
37</body>
38</html>

CORS (Flask-CORS)

 1from flask import Flask, jsonify
 2from flask_cors import CORS
 3
 4app = Flask(__name__)
 5
 6# Enable CORS for all routes
 7CORS(app)
 8
 9# Or configure specific origins
10CORS(app, resources={
11    r"/api/*": {
12        "origins": ["http://localhost:3000", "https://example.com"],
13        "methods": ["GET", "POST", "PUT", "DELETE"],
14        "allow_headers": ["Content-Type", "Authorization"]
15    }
16})
17
18@app.route('/api/data')
19def get_data():
20    return jsonify({'data': 'value'})

Error Handling

 1from flask import Flask, jsonify
 2
 3app = Flask(__name__)
 4
 5@app.errorhandler(404)
 6def not_found(error):
 7    return jsonify({'error': 'Not found'}), 404
 8
 9@app.errorhandler(500)
10def internal_error(error):
11    return jsonify({'error': 'Internal server error'}), 500
12
13@app.errorhandler(Exception)
14def handle_exception(e):
15    app.logger.error(f'Unhandled exception: {e}')
16    return jsonify({'error': 'Something went wrong'}), 500
17
18class ValidationError(Exception):
19    pass
20
21@app.errorhandler(ValidationError)
22def handle_validation_error(e):
23    return jsonify({'error': str(e)}), 400
24
25@app.route('/validate')
26def validate():
27    raise ValidationError('Invalid input')

Configuration

 1from flask import Flask
 2import os
 3
 4app = Flask(__name__)
 5
 6# Configuration from object
 7class Config:
 8    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
 9    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
10    SQLALCHEMY_TRACK_MODIFICATIONS = False
11    DEBUG = False
12    TESTING = False
13
14class DevelopmentConfig(Config):
15    DEBUG = True
16
17class ProductionConfig(Config):
18    DEBUG = False
19
20class TestingConfig(Config):
21    TESTING = True
22    SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db'
23
24# Load config
25config_name = os.environ.get('FLASK_ENV', 'development')
26if config_name == 'production':
27    app.config.from_object(ProductionConfig)
28elif config_name == 'testing':
29    app.config.from_object(TestingConfig)
30else:
31    app.config.from_object(DevelopmentConfig)
32
33# Or from file
34# app.config.from_pyfile('config.py')
35
36# Or from environment variable
37# app.config.from_envvar('APP_CONFIG_FILE')

Logging

 1from flask import Flask
 2import logging
 3from logging.handlers import RotatingFileHandler
 4import os
 5
 6app = Flask(__name__)
 7
 8if not app.debug:
 9    if not os.path.exists('logs'):
10        os.mkdir('logs')
11    
12    file_handler = RotatingFileHandler(
13        'logs/app.log',
14        maxBytes=10240,
15        backupCount=10
16    )
17    file_handler.setFormatter(logging.Formatter(
18        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
19    ))
20    file_handler.setLevel(logging.INFO)
21    app.logger.addHandler(file_handler)
22    
23    app.logger.setLevel(logging.INFO)
24    app.logger.info('Application startup')
25
26@app.route('/')
27def index():
28    app.logger.info('Index page accessed')
29    return 'Hello, World!'

Testing

 1# test_app.py
 2import pytest
 3from app import app, db, User
 4
 5@pytest.fixture
 6def client():
 7    app.config['TESTING'] = True
 8    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
 9    
10    with app.test_client() as client:
11        with app.app_context():
12            db.create_all()
13        yield client
14        with app.app_context():
15            db.drop_all()
16
17def test_index(client):
18    response = client.get('/')
19    assert response.status_code == 200
20    assert b'Hello' in response.data
21
22def test_create_user(client):
23    response = client.post('/users', json={
24        'username': 'testuser',
25        'email': 'test@example.com'
26    })
27    assert response.status_code == 201
28    data = response.get_json()
29    assert data['username'] == 'testuser'
30
31def test_get_users(client):
32    response = client.get('/users')
33    assert response.status_code == 200
34    data = response.get_json()
35    assert isinstance(data, list)
1# Run tests
2pytest test_app.py
3
4# With coverage
5pytest --cov=app test_app.py

RESTful API Example

 1from flask import Flask, request, jsonify
 2from flask_sqlalchemy import SQLAlchemy
 3
 4app = Flask(__name__)
 5app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///api.db'
 6db = SQLAlchemy(app)
 7
 8class Item(db.Model):
 9    id = db.Column(db.Integer, primary_key=True)
10    name = db.Column(db.String(100), nullable=False)
11    description = db.Column(db.Text)
12    price = db.Column(db.Float, nullable=False)
13    
14    def to_dict(self):
15        return {
16            'id': self.id,
17            'name': self.name,
18            'description': self.description,
19            'price': self.price
20        }
21
22with app.app_context():
23    db.create_all()
24
25# List all items
26@app.route('/api/items', methods=['GET'])
27def get_items():
28    items = Item.query.all()
29    return jsonify([item.to_dict() for item in items])
30
31# Get single item
32@app.route('/api/items/<int:item_id>', methods=['GET'])
33def get_item(item_id):
34    item = Item.query.get_or_404(item_id)
35    return jsonify(item.to_dict())
36
37# Create item
38@app.route('/api/items', methods=['POST'])
39def create_item():
40    data = request.get_json()
41    item = Item(
42        name=data['name'],
43        description=data.get('description', ''),
44        price=data['price']
45    )
46    db.session.add(item)
47    db.session.commit()
48    return jsonify(item.to_dict()), 201
49
50# Update item
51@app.route('/api/items/<int:item_id>', methods=['PUT'])
52def update_item(item_id):
53    item = Item.query.get_or_404(item_id)
54    data = request.get_json()
55    
56    item.name = data.get('name', item.name)
57    item.description = data.get('description', item.description)
58    item.price = data.get('price', item.price)
59    
60    db.session.commit()
61    return jsonify(item.to_dict())
62
63# Delete item
64@app.route('/api/items/<int:item_id>', methods=['DELETE'])
65def delete_item(item_id):
66    item = Item.query.get_or_404(item_id)
67    db.session.delete(item)
68    db.session.commit()
69    return '', 204
70
71if __name__ == '__main__':
72    app.run(debug=True)

Deployment

Gunicorn

1# Install
2pip install gunicorn
3
4# Run
5gunicorn -w 4 -b 0.0.0.0:8000 app:app
6
7# With config file
8gunicorn -c gunicorn_config.py app:app
 1# gunicorn_config.py
 2bind = "0.0.0.0:8000"
 3workers = 4
 4worker_class = "sync"
 5worker_connections = 1000
 6timeout = 30
 7keepalive = 2
 8errorlog = "logs/gunicorn-error.log"
 9accesslog = "logs/gunicorn-access.log"
10loglevel = "info"

Docker

 1# Dockerfile
 2FROM python:3.11-slim
 3
 4WORKDIR /app
 5
 6COPY requirements.txt .
 7RUN pip install --no-cache-dir -r requirements.txt
 8
 9COPY . .
10
11EXPOSE 8000
12
13CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]
 1# docker-compose.yml
 2version: '3.8'
 3
 4services:
 5  web:
 6    build: .
 7    ports:
 8      - "8000:8000"
 9    environment:
10      - FLASK_ENV=production
11      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
12    depends_on:
13      - db
14  
15  db:
16    image: postgres:15
17    environment:
18      - POSTGRES_USER=user
19      - POSTGRES_PASSWORD=pass
20      - POSTGRES_DB=mydb
21    volumes:
22      - postgres_data:/var/lib/postgresql/data
23
24volumes:
25  postgres_data:

Related Snippets