Summary
Flask is a lightweight, flexible Python web framework based on Werkzeug and Jinja2. It follows a minimalist philosophy, providing core web development functionality while allowing developers to choose their preferred tools and libraries. This cheatsheet covers Flask fundamentals, routing, templates, forms, database integration with SQLAlchemy, authentication, RESTful APIs, testing, and deployment best practices.
Flask Basics
Installation & Setup
pip install flask
Minimal Application
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
Application Factory Pattern
# app/__init__.py
def create_app(config_name='development'):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Register blueprints
from .main import main as main_bp
app.register_blueprint(main_bp)
return app
Routing & Views
Basic Routes
@app.route('/user/<username>')
def show_user(username):
return f'User: {username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post: {post_id}'
HTTP Methods
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_login()
return show_login_form()
URL Building
from flask import url_for
@app.route('/user/<username>')
def profile(username):
return f'Profile: {username}'
# Usage: url_for('profile', username='john')
Blueprints
# auth.py
from flask import Blueprint
auth = Blueprint('auth', __name__, url_prefix='/auth')
@auth.route('/login')
def login():
return 'Login page'
# main app
app.register_blueprint(auth)
Request Handling
Request Object
from flask import request
@app.route('/search')
def search():
# Query parameters
query = request.args.get('q', '')
# Form data
username = request.form.get('username')
# JSON data
data = request.get_json()
# Files
file = request.files['file']
# Headers
user_agent = request.headers.get('User-Agent')
# Cookies
session_id = request.cookies.get('session_id')
Response Object
from flask import make_response, jsonify
@app.route('/api/data')
def get_data():
# JSON response
return jsonify({'key': 'value'}), 200
# Custom response
resp = make_response('Custom response')
resp.headers['X-Custom-Header'] = 'value'
resp.set_cookie('session', 'abc123')
return resp
Templates (Jinja2)
Basic Template Rendering
from flask import render_template
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
Template Syntax
<!-- templates/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
<!-- templates/user.html -->
{% extends "base.html" %}
{% block title %}User Profile{% endblock %}
{% block content %}
<h1>Hello {{ name|upper }}!</h1>
{% if user.is_active %}
<p>Active user</p>
{% endif %}
{% for item in items %}
<li>{{ loop.index }}: {{ item }}</li>
{% endfor %}
{% endblock %}
Template Filters
@app.template_filter('reverse')
def reverse_filter(s):
return s[::-1]
# Usage in template: {{ name|reverse }}
Forms & Validation
Flask-WTF Forms
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Process form data
email = form.email.data
return redirect(url_for('index'))
return render_template('login.html', form=form)
CSRF Protection
# config.py
SECRET_KEY = 'your-secret-key'
# In template
<form method="POST">
{{ form.csrf_token }}
{{ form.email.label }} {{ form.email() }}
{{ form.submit() }}
</form>
Database Integration
Flask-SQLAlchemy Setup
from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
Models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f'<User {self.username}>'
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
Database Operations
# Create tables
db.create_all()
# Add record
user = User(username='john', email='john@example.com')
db.session.add(user)
db.session.commit()
# Query
all_users = User.query.all()
user = User.query.filter_by(username='john').first()
user = User.query.get(1) # by primary key
# Update
user.email = 'newemail@example.com'
db.session.commit()
# Delete
db.session.delete(user)
db.session.commit()
Migrations (Flask-Migrate)
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
Authentication & Sessions
Flask-Login
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'
class User(UserMixin, db.Model):
# ... model fields ...
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(email=email).first()
if user and check_password_hash(user.password, password):
login_user(user, remember=True)
return redirect(url_for('dashboard'))
return 'Invalid credentials'
@app.route('/dashboard')
@login_required
def dashboard():
return 'Welcome to dashboard'
Sessions
from flask import session
@app.route('/set_session')
def set_session():
session['username'] = 'john'
return 'Session set'
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
return f'Hello {username}'
RESTful APIs
Basic REST Endpoints
@app.route('/api/users', methods=['GET'])
def get_users():
users = User.query.all()
return jsonify([{'id': u.id, 'username': u.username} for u in users])
@app.route('/api/users/<int:id>', methods=['GET'])
def get_user(id):
user = User.query.get_or_404(id)
return jsonify({'id': user.id, 'username': user.username})
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify({'id': user.id}), 201
@app.route('/api/users/<int:id>', methods=['PUT'])
def update_user(id):
user = User.query.get_or_404(id)
data = request.get_json()
user.username = data.get('username', user.username)
db.session.commit()
return jsonify({'message': 'Updated'})
@app.route('/api/users/<int:id>', methods=['DELETE'])
def delete_user(id):
user = User.query.get_or_404(id)
db.session.delete(user)
db.session.commit()
return '', 204
Flask-RESTful
from flask_restful import Api, Resource, reqparse
api = Api(app)
class UserAPI(Resource):
def get(self, id=None):
if id:
user = User.query.get_or_404(id)
return {'id': user.id, 'username': user.username}
users = User.query.all()
return [{'id': u.id, 'username': u.username} for u in users]
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('username', required=True)
parser.add_argument('email', required=True)
args = parser.parse_args()
# Create user logic
return {'message': 'User created'}, 201
api.add_resource(UserAPI, '/api/users', '/api/users/<int:id>')
Error Handling
Error Handlers
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(e):
db.session.rollback()
return render_template('500.html'), 500
# API error handling
@app.errorhandler(404)
def api_not_found(e):
return jsonify({'error': 'Resource not found'}), 404
Custom Exceptions
class ValidationError(Exception):
pass
@app.errorhandler(ValidationError)
def handle_validation_error(e):
return jsonify({'error': str(e)}), 400
Testing
Unit Tests
import unittest
from app import create_app, db
class UserModelCase(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_password_hashing(self):
u = User(username='test')
u.set_password('cat')
self.assertFalse(u.check_password('dog'))
self.assertTrue(u.check_password('cat'))
Client Testing
def test_home_page(self):
client = self.app.test_client()
response = client.get('/')
self.assertEqual(response.status_code, 200)
self.assertIn(b'Welcome', response.data)
def test_login(self):
client = self.app.test_client()
response = client.post('/login', data={
'email': 'test@example.com',
'password': 'password'
}, follow_redirects=True)
self.assertEqual(response.status_code, 200)
Configuration & Deployment
Configuration Classes
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess'
SQLALCHEMY_TRACK_MODIFICATIONS = False
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
}
Environment Variables
# .env file
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://user:pass@localhost/dbname
# Load with python-dotenv
from dotenv import load_dotenv
load_dotenv()
Deployment (Gunicorn)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
Performance & Security
Caching
from flask_caching import Cache
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
@app.route('/expensive')
@cache.cached(timeout=300)
def expensive_operation():
# Time-consuming operation
return result
Rate Limiting
from flask_limiter import Limiter
limiter = Limiter(
app,
key_func=lambda: request.remote_addr,
default_limits=["100 per hour"]
)
@app.route('/api/data')
@limiter.limit("5 per minute")
def get_data():
return jsonify({'data': 'value'})
Security Headers
from flask_talisman import Talisman
Talisman(app, force_https=True)
# Manual headers
@app.after_request
def set_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
Password Hashing
from werkzeug.security import generate_password_hash, check_password_hash
# Store password
password_hash = generate_password_hash('password123')
# Verify password
check_password_hash(password_hash, 'password123') # Returns True
Key Concepts & Comparisons
Flask vs Django Comparison
| Aspect | Flask | Django |
|---|---|---|
| Philosophy | Micro-framework, minimal | Full-featured, "batteries included" |
| Learning Curve | Easier to start | Steeper learning curve |
| Flexibility | High flexibility | More structured/opinionated |
| Built-in Features | Minimal (Werkzeug, Jinja2) | ORM, Admin, Auth, etc. |
| Use Cases | APIs, small-medium apps | Large, complex applications |
Context Objects in Flask
| Context | Objects Available | Scope | Usage |
|---|---|---|---|
| Application Context | current_app, g |
Per application | Config access, app-level data |
| Request Context | request, session |
Per HTTP request | Request data, user sessions |
# Application context example
with app.app_context():
print(current_app.config['SECRET_KEY'])
g.db = get_database_connection()
# Request context (automatic in view functions)
@app.route('/profile')
def profile():
username = request.args.get('username')
user_id = session.get('user_id')
return f'Profile for {username}'
Blueprint Organization Benefits
| Benefit | Description | Example |
|---|---|---|
| Modularity | Organize related routes | auth.py, api.py, admin.py |
| Reusability | Share blueprints across projects | Authentication blueprint |
| Team Development | Multiple developers work on different modules | Feature-based separation |
| URL Prefixing | Group routes under common prefix | /api/v1/, /admin/ |
Database Connection Patterns
# Flask-SQLAlchemy pattern (recommended)
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
# Automatic connection management
@app.route('/users')
def get_users():
users = User.query.all() # Connection handled automatically
return jsonify([u.username for u in users])
# Manual session management
@app.teardown_appcontext
def close_database(error):
if hasattr(g, 'db'):
g.db.close()
Request-Response Cycle
- HTTP Request: Client sends request to Flask application
- URL Routing: Flask matches URL pattern to view function
- Before Request:
@app.before_requesthandlers execute - View Function: Matched function processes request
- Template Rendering: Jinja2 renders templates (if applicable)
- After Request:
@app.after_requesthandlers modify response - HTTP Response: Response sent back to client
File Upload Implementation
from werkzeug.utils import secure_filename
import os
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'File uploaded successfully'
return 'Upload failed'
WSGI and Deployment
| Concept | Description | Example |
|---|---|---|
| WSGI | Web Server Gateway Interface | Standard Python web interface |
| Development Server | Built-in Flask server | flask run (not for production) |
| Production Server | WSGI-compatible server | Gunicorn, uWSGI, Waitress |
| Reverse Proxy | Handle static files, SSL | Nginx, Apache |
Middleware Implementation Patterns
# Before request (authentication check)
@app.before_request
def require_auth():
if request.endpoint and request.endpoint.startswith('admin'):
if 'user_id' not in session:
return redirect(url_for('auth.login'))
# After request (add security headers)
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
# Error handling
@app.errorhandler(404)
def not_found(error):
if request.path.startswith('/api/'):
return jsonify({'error': 'Resource not found'}), 404
return render_template('404.html'), 404
Quick Reference Commands
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# Install Flask
pip install flask
# Run development server
flask run
export FLASK_APP=app.py
export FLASK_ENV=development
# Database commands
flask db init
flask db migrate
flask db upgrade
# Run tests
python -m pytest
python -m unittest discover
# Freeze requirements
pip freeze > requirements.txt
Essential Flask Extensions
| Category | Extension | Purpose | Usage |
|---|---|---|---|
| Database | Flask-SQLAlchemy | ORM and database toolkit | db.Model, db.session |
| Database | Flask-Migrate | Database migrations | flask db migrate |
| Authentication | Flask-Login | User session management | @login_required, current_user |
| Forms | Flask-WTF | Forms and CSRF protection | FlaskForm, CSRF tokens |
| API Development | Flask-RESTful | REST API framework | Resource classes |
| Authentication | Flask-JWT-Extended | JWT token authentication | @jwt_required |
| Communication | Flask-Mail | Email functionality | Send emails |
| Performance | Flask-Caching | Caching support | @cache.cached() |
| Security | Flask-Limiter | Rate limiting | API endpoint protection |
| CORS | Flask-CORS | Cross-origin requests | Enable CORS headers |
Performance Optimization Strategies
| Strategy | Implementation | Impact |
|---|---|---|
| Database Connection Pooling | SQLAlchemy engine configuration | Reduced connection overhead |
| Query Optimization | Eager loading, proper indexing | Faster database operations |
| Caching | Flask-Caching, Redis | Reduced computation time |
| Static File Serving | CDN, Nginx | Faster asset delivery |
| Response Compression | Gzip middleware | Reduced bandwidth usage |
| Pagination | Limit query results | Better memory usage |
| Async Views (Flask 2.0+) | async def for I/O operations |
Better concurrency |
| Application Profiling | Flask-Profiler, cProfile | Identify bottlenecks |
Security Best Practices Checklist
Essential Security Measures
- HTTPS Only: Force SSL/TLS in production environment
- Secure Cookies: Set
secure=True,httponly=Truefor session cookies - CSRF Protection: Use Flask-WTF for form protection
- Input Validation: Validate and sanitize all user inputs
- SQL Injection Prevention: Use SQLAlchemy parameterized queries
- Password Security: Use
werkzeug.securityfor hashing - Security Headers: Implement X-Frame-Options, CSP, etc.
- Dependency Management: Keep all packages updated
- Environment Variables: Store secrets in environment, not code
- Authentication & Authorization: Implement proper access controls
- Rate Limiting: Protect API endpoints from abuse
- Security Logging: Log authentication and authorization events
Security Implementation Examples
# Secure session configuration
app.config.update(
SECRET_KEY='your-secret-key',
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_HTTPONLY=True, # No JS access
SESSION_COOKIE_SAMESITE='Lax' # CSRF protection
)
# Security headers
@app.after_request
def security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
Thread Safety & Concurrency
| Aspect | Flask Behavior | Best Practice |
|---|---|---|
| Request Handling | Each request gets own context | No shared mutable state |
| Database Connections | Thread-local sessions | Use Flask-SQLAlchemy |
| Global Variables | Use g object for request data |
Avoid module-level globals |
| Caching | Thread-safe cache backends | Redis, Memcached |
Production Deployment Best Practices
✅ Server Setup: Use production WSGI server (Gunicorn, uWSGI)
✅ Environment: Set proper environment variables, use .env files
✅ Security: Enable HTTPS, set security headers, validate inputs
✅ Performance: Implement caching, use CDN, optimize database queries
✅ Monitoring: Configure logging, error tracking, health checks
✅ Scalability: Use reverse proxy (Nginx), implement rate limiting