Skip to content

Flask Best Practices

This guide covers best practices for developing Flask applications, helping you write more maintainable, scalable, and professional code.

Project Structure

Use Application Factory Pattern

python
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app(config_name='default'):
    app = Flask(__name__)
    app.config.from_object(f'config.{config_name}')
    
    # Initialize extensions
    db.init_app(app)
    
    # Register blueprints
    from app.main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    
    return app

Organize Code with Blueprints

python
# app/main/__init__.py
from flask import Blueprint

main = Blueprint('main', __name__)

from . import views, errors
myapp/
├── app/
│   ├── __init__.py
│   ├── models.py
│   ├── main/
│   │   ├── __init__.py
│   │   ├── views.py
│   │   └── errors.py
│   ├── auth/
│   │   ├── __init__.py
│   │   └── views.py
│   ├── static/
│   └── templates/
├── migrations/
├── tests/
├── venv/
├── config.py
├── requirements.txt
└── run.py

Configuration Management

Use Environment-Specific Configs

python
# config.py
import os

class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    
    @staticmethod
    def init_app(app):
        pass

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'

class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')

config = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

Use Environment Variables

python
# .env
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://user:pass@localhost/dbname
FLASK_ENV=development

Database Best Practices

Use Migrations

Always use Flask-Migrate for database schema changes:

bash
flask db init
flask db migrate -m "Initial migration"
flask db upgrade

Implement Proper Models

python
from datetime import datetime
from app import db

class User(db.Model):
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(120), unique=True, index=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def __repr__(self):
        return f'<User {self.username}>'

Use Database Sessions Properly

python
from app import db

# Good practice
try:
    user = User(username='john')
    db.session.add(user)
    db.session.commit()
except Exception as e:
    db.session.rollback()
    raise e
finally:
    db.session.close()

Error Handling

Implement Custom Error Pages

python
@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return render_template('500.html'), 500

Use Logging

python
import logging
from logging.handlers import RotatingFileHandler

if not app.debug:
    file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
    ))
    file_handler.setLevel(logging.INFO)
    app.logger.addHandler(file_handler)
    app.logger.setLevel(logging.INFO)
    app.logger.info('Application startup')

Security Best Practices

Protect Against CSRF

python
from flask_wtf.csrf import CSRFProtect

csrf = CSRFProtect()
csrf.init_app(app)

Use Strong Secret Keys

python
import secrets

# Generate a secure secret key
secret_key = secrets.token_hex(32)

Validate User Input

python
from flask_wtf import FlaskForm
from wtforms import StringField, validators

class RegistrationForm(FlaskForm):
    username = StringField('Username', [
        validators.Length(min=4, max=25),
        validators.Regexp('^[A-Za-z0-9_]+$')
    ])
    email = StringField('Email', [validators.Email()])

Performance Optimization

Use Caching

python
from flask_caching import Cache

cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app)

@app.route('/expensive')
@cache.cached(timeout=300)
def expensive_operation():
    # Expensive computation
    return result

Implement Pagination

python
@app.route('/users')
def users():
    page = request.args.get('page', 1, type=int)
    users = User.query.paginate(page=page, per_page=20)
    return render_template('users.html', users=users)

Use Database Connection Pooling

python
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
    'pool_size': 10,
    'pool_recycle': 3600,
    'pool_pre_ping': True
}

Testing

Write Unit Tests

python
import unittest
from app import create_app, db

class BasicTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()
        self.client = self.app.test_client()
    
    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()
    
    def test_home_page(self):
        response = self.client.get('/')
        self.assertEqual(response.status_code, 200)

Deployment

Use Production WSGI Server

Never use Flask's built-in server in production:

bash
# Use Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"

Set Up Proper Logging

python
# Production logging configuration
if not app.debug and not app.testing:
    if app.config['LOG_TO_STDOUT']:
        stream_handler = logging.StreamHandler()
        stream_handler.setLevel(logging.INFO)
        app.logger.addHandler(stream_handler)
    else:
        if not os.path.exists('logs'):
            os.mkdir('logs')
        file_handler = RotatingFileHandler('logs/app.log',
                                          maxBytes=10240, backupCount=10)
        file_handler.setFormatter(logging.Formatter(
            '%(asctime)s %(levelname)s: %(message)s '
            '[in %(pathname)s:%(lineno)d]'))
        file_handler.setLevel(logging.INFO)
        app.logger.addHandler(file_handler)

Code Quality

Follow PEP 8

Use tools like flake8 or black for code formatting:

bash
pip install black flake8
black app/
flake8 app/

Use Type Hints

python
from typing import Optional, List

def get_user(user_id: int) -> Optional[User]:
    return User.query.get(user_id)

def get_all_users() -> List[User]:
    return User.query.all()

Document Your Code

python
def process_payment(amount: float, currency: str = 'USD') -> dict:
    """
    Process a payment transaction.
    
    Args:
        amount: The payment amount
        currency: The currency code (default: USD)
    
    Returns:
        A dictionary containing transaction details
    
    Raises:
        ValueError: If amount is negative
    """
    if amount < 0:
        raise ValueError("Amount cannot be negative")
    # Process payment logic
    return {'status': 'success', 'amount': amount}

Summary

Following these best practices will help you:

  • Write more maintainable and scalable code
  • Improve application security
  • Enhance performance
  • Make debugging easier
  • Facilitate team collaboration

Remember that best practices evolve over time, so stay updated with the Flask community and documentation.

Content is for learning and research only.