Building Scalable Web Applications with Flask
Establish a standard directory layout to separate static assets, templates, and application logic. A typical organization includes:
- static/: Stores CSS, JS, and image files.
- templates/: Holds HTML templates processed by Jinja2.
- app.py: The main entry point for the WSGI application.
- modules/: Additional Python modules for scalability.
Application Factory Pattern
Instantiate the application object using the factory pattern to support blueprints and extensions.
from flask import Flask
def create_app(config_name='default'):
app = Flask(__name__)
app.config.from_object(factories.{config_name})
return app
if __name__ == '__main__':
web_app = create_app()
web_app.run(host='0.0.0.0', debug=True)
Request Routing and Methods
Define routes to handle HTTP requests. Specify allowed methods explicitly to enforce protocol correctness.
@app.route('/data/extract/<int:item_id>', methods=['GET', 'POST'])
def get_data(item_id):
if request.method == 'POST':
# Handle form submission
pass
elif item_id < 0:
abort(400)
return {'id': item_id}
Query parameters can be retrieved when path arguments are not sufficient:
@app.route('/search/')
def search_query():
keyword = request.args.get('q')
limit = request.args.get('limit', 10)
return render_search(keyword, limit)
URL generation is handled programmatically to avoid hardcoding paths:
redirect_url = url_for('static', filename='logo.png')
avail_path = url_for('home_page', page=2)
Configuration Management
Settings should be externalized from code. Use environment variables or distinct configuration classes.
# config.py
class Config:
DEBUG = True
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
To apply settings in the main instance:
app.config['JSON_AS_ASCII'] = False
Or load from an object:
app.config.from_object(Config)
Response Handling
Return JSON responses directly using the provided utilities:
from flask import jsonify
def api_response(data, status_code=200):
return jsonify(data), status_code
Templating with Jinja2
Templates allow separation of logic and presentation. Variables passed from view are rendered using double curly braces.
return render_template('layout.html', title='Home', user={'name': 'Alice'})
Inside index.html:
<h1>Hello {{ user.name }}</h1>
<p>Age: {{ user.age|default(18) }}</p>
Filters and Control Flow
Built-in filters modify output dynamically:
<!-- Length filter -->
{{ content|length }}
<!-- Math operations -->
{% set total = items|map(attribute='price')|sum %}
<!-- Looping -->
{% for post in posts %}
<div>{{ post.title }}</div>
{% endfor %}
Inheritance reduces duplication in layout structures:
<!-- base.html -->
{% block head %}<style>body { font-family: sans-serif; }</style>{% endblock %}
<body>{% block body %}{% endblock %}</body>
<!-- child.html -->
{% extends "base.html" %}
{% block body %}<h1>Welcome</h1>{% endblock %}
Modular Design with Blueprints
Large applications split functionality into registered blueprints. This isolates URL prefixes and resources.
# module_auth.py
from flask import Blueprint
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login')
def login_form():
return "Login Page"
# app.py
from module_auth import auth_bp
app.register_blueprint(auth_bp)
Database Integration (SQLAlchemy)
The SQLAlchemy ORM abstracts database interactions. Define models inheriting from db.Model.
class Author(db.Model):
__tablename__ = 'authors'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True)
posts = db.relationship('Post', backref='writer', lazy='dynamic')
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('authors.id'))
content = db.Column(db.Text)
Performing CRUD operations involves session management:
new_book = Book(title='New Release', price=19.99)
db.session.add(new_book)
db.session.commit()
# Querying
book_instance = Book.query.filter_by(price=19.99).first()
# Updates
book_instance.content = 'Updated Content'
db.session.commit()
# Deletion
Book.query.filter_by(id=1).delete()
db.session.commit()
State Management
Cookies are stored on the client, while sessions reside server-side but require a secret key.
# Cookies
response = make_response("Cookie Set")
response.set_cookie('session_token', token_value, max_age=3600)
# Session Access
username = request.cookies.get('session_token')
# Server-side Sessions
app.config['SECRET_KEY'] = 'super_secure_key'
session['user_id'] = 101
retrieved_id = session.get('user_id')
Form Validation
Use WTForms to validate input before processing business logic.
# forms.py
class RegistrationForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[Length(min=6)])
submit = SubmitField('Submit')
Process in the view:
form = RegistrationForm(
formdata=request.form,
obj=current_user
)
if form.validate_on_submit():
# Process valid data
pass
else:
return form.errors
Security Best Practices
Never store plain-text passwords. Hash credentials using Werkzeug.
from werkzeug.security import generate_password_hash, check_password_hash
hashed_pw = generate_password_hash('clear_text_password')
is_valid = check_password_hash(hashed_pw, 'clear_text_password')
Generate verification codes programmatically:
import random, string
letters = string.ascii_letters + string.digits
code = ''.join(random.sample(letters, 6))
External Services Integration
Mailing
Configure SMTP settings externally and send via flask-mail.
mail.send(Message('Subject', recipients=['user@example.com'], html='<p>Body</p>'))
CORS Support
Allow cross-origin requests when serving APIs to frontends hosted elsewhere.
from flask_cors import CORS
CORS(app, resources={r"/*": {"origins": "*"}})
Alternatively, set headers manually on responses:
response = jsonify({'status': 'ok'})
response.headers['Access-Control-Allow-Origin'] = '*'