Python Web Development with Flask
Flask is a lightweight Python web framework perfect for building web applications and APIs. It’s designed to make getting started quick and easy while being powerful enough for complex applications.
Why Choose Flask?
- Lightweight: Minimal core, easy to understand
- Flexible: Choose your own tools and libraries
- Beginner Friendly: Excellent documentation and gentle learning curve
- Extensible: Rich ecosystem of extensions
- Production Ready: Used by companies worldwide
- Great for APIs: Perfect for REST API development
Installation
Basic Installation
# Install Flask
pip install flask
# Verify installation
python -c "import flask; print('Flask installed successfully!')"Virtual Environment (Recommended)
# Create virtual environment
python -m venv flask_app
# Activate on Windows
flask_app\Scripts\activate
# Activate on Mac/Linux
source flask_app/bin/activate
# Install Flask in virtual environment
pip install flask
# Deactivate when done
deactivateYour First Flask App
Basic Hello World
Create a file called app.py:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return '<h1>Hello, World!</h1>'
if __name__ == '__main__':
app.run(debug=True)Run your application:
python app.pyVisit http://localhost:5000 in your browser to see “Hello, World!”
Understanding the Code
Flask(__name__): Creates Flask application@app.route('/'): Decorator that defines URL routedebug=True: Enables development server with auto-reload
Routing
Basic Routes
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return '<h1>Welcome to my website!</h1>'
@app.route('/about')
def about():
return '<h1>About this website</h1>'
@app.route('/contact')
def contact():
return '<h1>Contact us at [email protected]</h1>'
if __name__ == '__main__':
app.run(debug=True)Dynamic Routes with Parameters
@app.route('/user/<username>')
def user_profile(username):
return f'<h1>Profile for {username}</h1>'
@app.route('/product/<int:product_id>')
def show_product(product_id):
return f'<h1>Product ID: {product_id}</h1>'
@app.route('/search/<query>')
def search_results(query):
return f'<h1>Search results for: {query}</h1>'Route Methods
from flask import request
@app.route('/api/data', methods=['GET'])
def get_data():
return {'message': 'GET request received'}
@app.route('/api/data', methods=['POST'])
def create_data():
data = request.get_json()
return {'message': 'Data created', 'received': data}, 201
@app.route('/api/data/<int:id>', methods=['PUT'])
def update_data(id):
return {'message': f'Data {id} updated'}
@app.route('/api/data/<int:id>', methods=['DELETE'])
def delete_data(id):
return {'message': f'Data {id} deleted'}Templates
Using HTML Templates
Create a templates folder and add index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: 0 auto; }
.header { text-align: center; margin-bottom: 30px; }
.content { background: #f5f5f5; padding: 20px; border-radius: 8px; }
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</header>
<main class="content">
{{ content }}
</main>
</div>
</body>
</html>Update your Flask app:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html',
title='My Flask App',
description='Welcome to my awesome website!',
content='This is the main content area.')Template Inheritance
Create base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Flask App{% endblock %}</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
nav { background: #333; color: white; padding: 1rem; margin-bottom: 20px; }
nav a { color: white; margin-right: 1rem; text-decoration: none; }
.content { max-width: 800px; margin: 0 auto; }
</style>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<div class="content">
{% block content %}{% endblock %}
</div>
</body>
</html>Create child templates:
<!-- home.html -->
{% extends "base.html" %}
{% block title %}Home - {% endblock %}
{% block content %}
<h1>Welcome to My Website</h1>
<p>This is the home page content.</p>
{% endblock %}
<!-- about.html -->
{% extends "base.html" %}
{% block title %}About - {% endblock %}
{% block content %}
<h1>About Us</h1>
<p>We are a company that builds amazing web applications with Flask!</p>
{% endblock %}Update your Flask app:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('home.html')
@app.route('/about')
def about():
return render_template('about.html')Forms and User Input
HTML Forms
Create templates/login.html:
{% extends "base.html" %}
{% block title %}Login - {% endblock %}
{% block content %}
<h1>Login</h1>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
{% endblock %}Form Handling in Flask
from flask import Flask, render_template, request, redirect, url_for, flash, session
app = Flask(__name__)
app.secret_key = 'your-secret-key-here' # Required for sessions
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
# Simple validation (use real authentication in production)
if username == 'admin' and password == 'password123':
session['logged_in'] = True
session['username'] = username
flash('Login successful!', 'success')
return redirect(url_for('dashboard'))
else:
flash('Invalid username or password', 'error')
return render_template('login.html')
@app.route('/dashboard')
def dashboard():
if not session.get('logged_in'):
return redirect(url_for('login'))
username = session.get('username')
return f'<h1>Welcome to your dashboard, {username}!</h1>'
@app.route('/logout')
def logout():
session.clear()
flash('You have been logged out', 'info')
return redirect(url_for('login'))Displaying Flash Messages
Add this to your base template:
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash flash-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<style>
.flash { padding: 10px; margin-bottom: 20px; border-radius: 4px; }
.flash-success { background: #d4edda; color: #155724; }
.flash-error { background: #f8d7da; color: #721c24; }
.flash-info { background: #d1ecf1; color: #0c5460; }
</style>Working with Databases
Using SQLite
Install Flask-SQLAlchemy:
pip install flask-sqlalchemyDatabase models:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
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)
password = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
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(200), nullable=False)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
def __repr__(self):
return f'<Post {self.title}>'
# Create database tables
with app.app_context():
db.create_all()CRUD Operations
from flask import render_template, request, redirect, url_for, flash
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
# Check if user already exists
if User.query.filter_by(username=username).first():
flash('Username already exists', 'error')
return redirect(url_for('register'))
# Create new user (hash password in real app!)
new_user = User(username=username, email=email, password=password)
db.session.add(new_user)
db.session.commit()
flash('Registration successful!', 'success')
return redirect(url_for('login'))
return render_template('register.html')
@app.route('/posts')
def posts():
all_posts = Post.query.order_by(Post.created_at.desc()).all()
return render_template('posts.html', posts=all_posts)
@app.route('/post/new', methods=['GET', 'POST'])
def new_post():
if request.method == 'POST':
title = request.form.get('title')
content = request.form.get('content')
new_post = Post(title=title, content=content, user_id=1) # Hardcoded user ID
db.session.add(new_post)
db.session.commit()
flash('Post created successfully!', 'success')
return redirect(url_for('posts'))
return render_template('new_post.html')APIs with Flask
RESTful API
from flask import Flask, jsonify, request
from models import db, User, Post
@app.route('/api/users', methods=['GET'])
def get_users():
users = User.query.all()
return jsonify([{
'id': user.id,
'username': user.username,
'email': user.email,
'created_at': user.created_at.isoformat()
} for user in users])
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = User.query.get_or_404(user_id)
return jsonify({
'id': user.id,
'username': user.username,
'email': user.email,
'created_at': user.created_at.isoformat()
})
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data or not data.get('username') or not data.get('email'):
return jsonify({'error': 'Username and email required'}), 400
# Check if user exists
if User.query.filter_by(username=data['username']).first():
return jsonify({'error': 'Username already exists'}), 400
user = User(username=data['username'], email=data['email'])
db.session.add(user)
db.session.commit()
return jsonify({
'id': user.id,
'username': user.username,
'email': user.email
}), 201API Authentication with JWT
Install required packages:
pip install flask-jwt-extended
pip install bcryptAuthentication setup:
from flask import Flask, request, jsonify
from flask_jwt_extended import JWTManager, jwt_required, create_access_token
import bcrypt
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-super-secret-jwt-key'
jwt = JWTManager(app)
@app.route('/api/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(username=username).first()
if not user or not bcrypt.checkpw(password.encode('utf-8'), user.password.encode('utf-8')):
return jsonify({'error': 'Invalid credentials'}), 401
access_token = create_access_token(identity=user.id)
return jsonify({
'access_token': access_token,
'user': {
'id': user.id,
'username': user.username
}
})
@app.route('/api/profile')
@jwt_required()
def profile():
current_user_id = get_jwt_identity()
user = User.query.get(current_user_id)
return jsonify({
'id': user.id,
'username': user.username,
'email': user.email
})File Uploads
Basic File Upload
from flask import Flask, request, render_template, redirect, url_for
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
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':
if 'file' not in request.files:
return render_template('upload.html', error='No file selected')
file = request.files['file']
if file.filename == '':
return render_template('upload.html', error='No file selected')
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
else:
return render_template('upload.html', error='File type not allowed')
return render_template('upload.html')Upload template:
{% extends "base.html" %}
{% block title %}Upload File - {% endblock %}
{% block content %}
<h1>Upload a File</h1>
{% if error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST" enctype="multipart/form-data">
<div>
<label for="file">Choose file:</label>
<input type="file" name="file" id="file">
</div>
<div>
<button type="submit">Upload</button>
</div>
</form>
{% endblock %}Error Handling
Custom Error Handlers
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
return render_template('500.html'), 500
@app.errorhandler(403)
def forbidden_error(error):
return render_template('403.html'), 403Global Error Handling
from flask import Flask, request, jsonify
import logging
app = Flask(__name__)
# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
@app.before_request
def before_request():
logger.info(f"Request: {request.method} {request.url}")
@app.after_request
def after_request(response):
logger.info(f"Response status: {response.status_code}")
return response
@app.errorhandler(Exception)
def handle_exception(e):
logger.error(f"Unhandled exception: {str(e)}")
if request.accept_mimetypes.accept_json:
return jsonify({'error': 'Internal server error'}), 500
else:
return render_template('error.html', error=str(e)), 500Flask Extensions
Popular Extensions
# Database
pip install flask-sqlalchemy
pip install flask-migrate
# Authentication
pip install flask-login
pip install flask-jwt-extended
# Forms
pip install flask-wtf
pip install wtforms
# Caching
pip install flask-caching
pip install redis
# Admin interface
pip install flask-admin
# REST API
pip install flask-restful
pip install flask-marshmallowUsing Flask-WTF
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
class LoginForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=4, max=25)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password',
validators=[DataRequired(), Length(min=6)])
submit = SubmitField('Login')
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Process form data
username = form.username.data
password = form.password.data
# Authentication logic here
return render_template('login.html', form=form)Deployment
Gunicorn Production Server
# Install production server
pip install gunicorn
# Run with Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app
# Configuration file (gunicorn.conf.py)
bind = "0.0.0.0:8000"
workers = 4
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
timeout = 30
keepalive = 2Docker Deployment
Create Dockerfile:
FROM python:3.9-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
# Expose port
EXPOSE 5000
# Run application
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"]Create requirements.txt:
Flask==2.3.2
Flask-SQLAlchemy==1.0.0
Flask-WTF==1.0.0
gunicorn==20.1.0Docker Compose
Create docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
volumes:
- ./uploads:/app/uploads
depends_on:
- db
db:
image: postgres:13
environment:
- POSTGRES_DB=flask_app
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/dataComplete Project Example
Blog Application Structure
flask_blog/
├── app.py
├── requirements.txt
├── config.py
├── models.py
├── forms.py
├── templates/
│ ├── base.html
│ ├── index.html
│ ├── login.html
│ ├── register.html
│ └── dashboard.html
├── static/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── main.js
└── uploads/Main application file:
from flask import Flask, render_template, redirect, url_for, flash, session
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
import os
app = Flask(__name__)
app.config.from_pyfile('config.py')
db = SQLAlchemy(app)
# Import models and forms
from models import User, Post
from forms import LoginForm, RegistrationForm
# Routes
@app.route('/')
def index():
posts = Post.query.order_by(Post.created_at.desc()).limit(5).all()
return render_template('index.html', posts=posts)
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
# Login logic here
session['user_id'] = form.user.id
flash('Login successful!', 'success')
return redirect(url_for('dashboard'))
return render_template('login.html', form=form)
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)Best Practices
Security Tips
# Never use eval() with user input
# Always validate and sanitize input
# Use parameterized queries for database operations
# Store secrets in environment variables, not code
# Use HTTPS in production
# Implement proper authentication and authorization
# Keep dependencies updated
# Good: Using parameterized queries
user = User.query.filter_by(username=username).first()
# Bad: String concatenation (SQL injection risk)
query = f"SELECT * FROM user WHERE username = '{username}'"Performance Tips
# Use database indexes
# Implement caching for expensive operations
# Use connection pooling
# Optimize database queries
# Use lazy loading for relationships
# Monitor application performance
# Use pagination for large datasets
# Example of pagination
@app.route('/posts')
def posts():
page = request.args.get('page', 1, type=int)
per_page = 10
posts = Post.query.offset((page - 1) * per_page).limit(per_page).all()
return render_template('posts.html', posts=posts, page=page)External Resources:
Related Tutorials:
Last updated on