↑
Documentation Index
Introduction
Application Overview
Routing
variable Rules
URL Building
HTTP Methods
Templates
Static Files
Request Object
Sending Form Data to Template
Cookies
Sessions
Redirect & Errors
Message Flashing
File Uploading
Extensions
Mail
SQLite
Deployment
Introduction to Flask
What Is Flask?
Flask is a lightweight, flexible, and minimalistic web framework for Python.
It follows the WSGI standard and promotes a micro-framework philosophy:
Keep the core small and simple,
Allow developers to add only what they need via extensions.
Flask is ideal for:
Small to medium web applications,
REST APIs and JSON services,
Rapid prototyping,
Learning how HTTP servers work internally.
The philosophy: “Simple core, powerful when extended.”
Installing Flask
Create a virtual environment (recommended):
python3 -m venv venv
source venv/bin/activate
pip install flask
Your First Flask Application
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, Flask!"
if __name__ == "__main__":
app.run(debug=True)
Explanation:
Flask(__name__) creates your web application object.
@app.route("/") maps the URL / to the function home().
debug=True enables automatic reloading and developer-friendly errors.
Run it:
python3 app.py
Visit http://127.0.0.1:5000 in the browser.
Routing: Connecting URLs to Functions
A route defines how your Flask application responds to a URL.
Each route corresponds to a view function.
@app.route("/hello")
def greet():
return "Hello!"
You can define dynamic routes:
@app.route("/user/<name>")
def user(name):
return f"User: {name}"
@app.route("/square/<int:num>")
def square(num):
return str(num * num)
Templates: Rendering HTML with Jinja2
Flask includes the Jinja2 templating engine for generating HTML.
Folder structure:
project/
├── app.py
└── templates/
└── index.html
<!-- templates/index.html -->
<h1>Hello, {{ name }}!</h1>
from flask import render_template
@app.route("/hello/<name>")
def hello(name):
return render_template("index.html", name=name)
Jinja2 allows loops, conditions, inheritance, and more.
Handling POST Requests
By default, routes accept GET requests. To handle POST, specify the method:
@app.route("/submit", methods=["GET", "POST"])
def submit():
if request.method == "POST":
data = request.form["username"]
return f"Received: {data}"
return "Send a POST request!"
Using JSON and Building APIs
Flask makes it easy to build REST APIs or JSON endpoints:
from flask import jsonify
@app.route("/api/user/<int:id>")
def api_user(id):
return jsonify({"id": id, "name": "Alice"})
jsonify automatically converts Python dictionaries into JSON responses.
Flask Application Structure (Recommended Layout)
For larger apps, organize code like this:
myapp/
├── app.py
├── static/
│ └── style.css
├── templates/
│ ├── base.html
│ └── index.html
└── routes/
└── home.py
Flask doesn't force you into a rigid folder structure — this flexibility is one of its strengths.
Development vs. Production
Flask's built-in development server is not meant for production.
Use a WSGI server such as:
Gunicorn,
uWSGI,
Waitress (Windows).
Then run Flask under the WSGI server.
Flask Extensions (Powerful Add-ons)
Flask-SQLAlchemy — ORM for relational databases
Flask-Migrate — database migrations
Flask-Login — authentication and session management
Flask-WTF — form handling and CSRF protection
Flask-Admin — admin dashboard
Flask-RESTful — build REST APIs more easily
Flask Application Overview
What Is Flask?
Flask is a lightweight, flexible, and minimalistic Python web framework.
It follows a micro-framework philosophy:
no built-in ORM
no built-in template engine (uses Jinja2)
no strict project structure
everything is extensible via extensions
Flask is commonly used for:
REST APIs
small to mid-sized web applications
prototypes and rapid development
microservices
Main advantages:
simple and intuitive API
minimal boilerplate
easy to integrate with SQLAlchemy, Jinja2, WTForms
large ecosystem of extensions
Installing and Running a Flask Application
pip install Flask
Minimal Flask app (app.py):
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, Flask!"
if __name__ == "__main__":
app.run(debug=True)
Run the app:
python app.py
Understanding the Flask Application Object
The Flask object represents your application.
Created using:
app = Flask(__name__)
Responsibilities:
handling incoming requests
mapping URLs to functions (routing)
managing application-wide configuration
initializing extensions
managing Jinja2 templates
__name__ helps Flask locate resources (templates, static files).
Routing Fundamentals
Routes connect URLs to Python functions (called view functions ).
Basic route:
@app.route("/about")
def about():
return "About page"
Routes can accept parameters:
@app.route("/user/<username>")
def profile(username):
return f"Hello, {username}"
Routes can restrict parameter types:
@app.route("/item/<int:id>")
def item(id):
return f"Item {id}"
Template Rendering
Flask uses the Jinja2 template engine.
Render templates:
from flask import render_template
@app.route("/hello")
def hello():
return render_template("hello.html", name="Junzhe")
Example templates/hello.html:
<h1>Hello {{ name }}!</h1>
Handling Requests and Forms
Flask provides the request object:
from flask import request
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
return f"Logged in as {username}"
return render_template("login.html")
Use cases:
processing HTML forms
AJAX requests
JSON APIs
Accessing JSON:
data = request.get_json()
Flask Application Configuration
Configuration values stored in app.config.
Common settings:
DEBUG = True
SECRET_KEY = "super-secret"
DATABASE_URI = "sqlite:///app.db"
Load configuration from Python file:
app.config.from_pyfile("config.py")
Or from environment variables:
app.config.from_envvar("APP_SETTINGS")
Using Flask Extensions
Flask achieves rich functionality through extensions:
Extension
Description
Flask-SQLAlchemy
Object-relational mapping (ORM) for database models.
Flask-Migrate
Database migrations built on top of Alembic.
Flask-WTF
Form handling, validation, and CSRF protection.
Flask-Login
User authentication and session management.
Flask-Mail
Email sending integration for Flask applications.
Flask-RESTful
REST API framework for building JSON APIs.
Extensions typically follow this pattern:
db = SQLAlchemy()
login = LoginManager()
def create_app():
app = Flask(__name__)
db.init_app(app)
login.init_app(app)
return app
The Application Factory Pattern
Large applications use a factory to create the app instance.
Allows:
testing flexibility
extension initialization
different configurations
Example:
def create_app(config_name="default"):
app = Flask(__name__)
app.config.from_object(config_name)
return app
Blueprints (Modular Applications)
Blueprints allow you to break the application into reusable modules.
Create a blueprint:
from flask import Blueprint
bp = Blueprint("auth", __name__)
@bp.route("/login")
def login():
return "Login page"
Register it:
app.register_blueprint(bp, url_prefix="/auth")
Static Files and Templates
project/
static/
css/
js/
images/
templates/
.html files
Serve static files:
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
Error Handling
@app.errorhandler(404)
def not_found(e):
return render_template("404.html"), 404
Useful for user experience and debugging.
Flask Routing
What Is Routing in Flask?
Routing is the mechanism that maps a URL path to a Python function (called a view function ).
Defined using the @app.route() decorator.
Flask uses Werkzeug’s routing system under the hood.
Basic Route Definition
@app.route("/")
def home():
return "Hello!"
Routes must return either:
a string
a Response object
a tuple (body, status code, headers)
Defining Routes with URLs
@app.route("/about")
def about():
return "About Page"
Flask automatically handles trailing slashes: /about/ redirects to /about
Dynamic URL Parameters
URLs can contain dynamic segments:
@app.route("/user/<username>")
def profile(username):
return f"User: {username}"
Example request:
/user/junzhe
Function receives:
username = "junzhe"
Available converters:
string (default)
int
float
path (accepts slashes)
uuid
Examples:
@app.route("/item/<int:item_id>")
def item(item_id):
return f"Item ID = {item_id}"
@app.route("/files/<path:filepath>")
def files(filepath):
return f"File Path = {filepath}"
URL Building with url_for()
Instead of hardcoding URLs, Flask encourages url_for:
from flask import url_for
@app.route("/")
def index():
return url_for("hello")
@app.route("/hello")
def hello():
return "Hi!"
URL with parameters:
url_for("profile", username="Alice")
Benefits:
automatic URL creation
avoiding broken links
routes remain consistent if changed later
HTTP Methods (GET, POST, etc.)
Routes accept only GET by default.
To enable POST or other methods:
@app.route("/submit", methods=["POST"])
def submit():
return "Form submitted"
Multiple methods:
@app.route("/api/user", methods=["GET", "POST"])
def api_user():
if request.method == "POST":
return "Created"
return "Listing users"
Handling Query Parameters
from flask import request
@app.route("/search")
def search():
keyword = request.args.get("q")
return f"Keyword: {keyword}"
Example request:
/search?q=python
Route Defaults
Set default values for parameters:
@app.route("/page/<int:num>")
@app.route("/page", defaults={"num": 1})
def page(num):
return f"Page {num}"
Useful for pagination or optional params.
Redirects
Use redirect together with url_for:
from flask import redirect
@app.route("/old")
def old():
return redirect(url_for("new"))
@app.route("/new")
def new():
return "This is the new page"
Custom Error Routes
Define custom error handlers:
@app.errorhandler(404)
def not_found(e):
return "Page not found!", 404
Common handlers:
404 — Not Found
500 — Server Error
405 — Method Not Allowed
Routing Inside Blueprints
Blueprints let you group related routes.
Create blueprint:
from flask import Blueprint
auth = Blueprint("auth", __name__)
@auth.route("/login")
def login():
return "Login Page"
Register blueprint:
app.register_blueprint(auth, url_prefix="/auth")
Final route becomes:
/auth/login
Advanced Routing Features
from werkzeug.routing import BaseConverter
class EvenIntConverter(BaseConverter):
regex = r"\d*[02468]"
app.url_map.converters["even"] = EvenIntConverter
@app.route("/number/<even:n>")
def even_number(n):
return f"Even: {n}"
Subdomain Routing:
@app.route("/", subdomain="api")
def api_home():
return "API Home"
Strict Slash Control:
@app.route("/users/", strict_slashes=False)
def users():
return "Users"
Flask Variable Rules
What Are Variable Rules in Flask?
In Flask, variable rules let you capture parts of the URL as parameters and pass them into your view functions.
They are defined directly inside the route pattern using angle brackets, such as <username> or <int:id>.
Example idea:
/user/junzhe → passes "junzhe" to the view function
/post/42 → passes 42 as an integer
Variable rules make routes more dynamic and expressive, which is essential for REST-style APIs.
Basic Syntax of Variable Rules
from flask import Flask
app = Flask(__name__)
@app.route('/user/<username>')
def show_user_profile(username):
return f'User: {username}'
Key points:
Everything inside <...> is treated as a variable name.
The variable name becomes a parameter of the view function.
By default, the value is a string.
Built-In Converters
Flask provides several converters to control the type and shape of URL variables:
Converter
Meaning
Example URL Rule
string
(default) any text without slashes
/hello/<string:name>
int
only integers
/post/<int:post_id>
float
floating-point numbers
/price/<float:value>
path
string including slashes
/files/<path:subpath>
uuid
UUID strings converted to UUID objects
/item/<uuid:item_id>
Syntax with a converter:
@app.route('/post/<int:post_id>')
def show_post(post_id):
# post_id is already an int
return f'Post ID: {post_id}'
Default Converter: string
If you omit a converter, Flask uses the string converter by default.
@app.route('/hello/<name>')
def hello(name):
# name is a string
return f'Hello, {name}!'
The following are equivalent:
@app.route('/hello/<name>')
@app.route('/hello/<string:name>')
def hello(name):
...
Using the int Converter
The int converter ensures that only integer values match the route.
@app.route('/post/<int:post_id>')
def show_post(post_id):
# If the URL is /post/abc, this route will NOT match.
# If the URL is /post/42, post_id == 42 (int).
return f'Post ID is {post_id}'
Using the float and path Converters
@app.route('/discount/<float:rate>')
def show_discount(rate):
return f'Discount rate: {rate:.2f}'
path converter (includes slashes):
@app.route('/files/<path:subpath>')
def show_file(subpath):
# Matches /files/images/logo.png
# subpath == 'images/logo.png'
return f'Serving: {subpath}'
path is useful for nested directories or "catch-all" URLs.
Using the uuid Converter
The uuid converter parses a standard UUID string into a Python UUID object.
from uuid import UUID
from flask import Flask
app = Flask(__name__)
@app.route('/items/<uuid:item_id>')
def get_item(item_id: UUID):
# item_id is a UUID instance, not a plain string
return f'Item ID: {item_id}'
This is very convenient for REST APIs that use UUIDs as primary identifiers.
Multiple Variable Rules in One Route
You can define multiple variables inside the same URL pattern.
@app.route('/shop/<category>/item/<int:item_id>')
def show_item(category, item_id):
return f'Category: {category}, Item ID: {item_id}'
Flask maps each variable to a separate function parameter in the same order as their appearance.
Make sure the parameter names in the function match exactly the variable names in the route.
Variable Rules and url_for()
When using url_for(), route variables become keyword arguments.
from flask import url_for
@app.route('/user/<username>')
def profile(username):
return f'Profile: {username}'
@app.route('/where-is-junzhe')
def where_is_junzhe():
# Generates /user/Junzhe
return url_for('profile', username='Junzhe')
Trailing Slashes and Variable Rules
Flask treats routes with and without trailing slashes differently:
@app.route('/hello/<name>') # no trailing slash
def hello(name):
...
@app.route('/hello2/<name>/') # with trailing slash
def hello2(name):
...
Behavior:
/hello/Bob → works
/hello/Bob/ → 404 (by default)
/hello2/Bob/ → works
/hello2/Bob → may redirect to /hello2/Bob/
Be consistent with your trailing slash style to avoid confusion.
Custom Converters (Overview)
Flask allows you to create custom converters for more complex validation rules.
Typical use cases:
restricting strings to certain patterns (e.g. usernames, slugs)
matching date formats
matching serialized IDs
Basic steps to create a custom converter:
Subclass werkzeug.routing.BaseConverter.
Register it on app.url_map.converters.
Use it in routes like <custom:name>.
Example (slug converter):
from werkzeug.routing import BaseConverter
class SlugConverter(BaseConverter):
regex = r'[a-z0-9-]+'
app.url_map.converters['slug'] = SlugConverter
@app.route('/blog/<slug:post_slug>')
def show_blog_post(post_slug):
return f'Post slug: {post_slug}'
Custom converters keep your routes clean and reusable.
Error Handling with Variable Rules
If a URL does not match the variable rule, Flask returns a 404 Not Found before entering your view.
Examples:
/post/abc does not match /post/<int:post_id>, so Flask returns 404.
/price/1.23 matches /price/<float:value>, but /price/abc does not.
You can provide a custom 404 handler if you want more user-friendly messages.
@app.errorhandler(404)
def page_not_found(e):
return 'Custom 404 page', 404
Flask URL Building
What Is URL Building in Flask?
URL building means generating URLs from view function names (endpoints) instead of hardcoding paths as strings.
Flask provides the url_for() helper to build URLs based on:
the endpoint name (usually the view function name)
any variable parts (path parameters)
optional query parameters
Advantages:
no hardcoded URL strings scattered in templates and Python code
safer refactoring: renaming or changing routes in one place
supports blueprints, static files, and external URLs
Basic Usage of url_for() in Python
Typical pattern inside your Python code:
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
# Build URL for the 'hello' endpoint
return url_for('hello')
@app.route('/hello')
def hello():
return 'Hello!'
Explanation:
'hello' is the endpoint name (the function name by default).
url_for('hello') returns '/hello'.
If you later change the route to /greeting, only the route definition needs updating, not every link in your app.
URL Building with Variable Rules
For routes with path parameters, pass them as keyword arguments to url_for():
@app.route('/user/<username>')
def profile(username):
return f'Profile: {username}'
@app.route('/where-is-junzhe')
def where_is_junzhe():
# Generates /user/Junzhe
return url_for('profile', username='Junzhe')
Important:
Keyword names must match the variable names in the route (username in this example).
Type converters (like <int:id>) are automatically handled as long as you pass compatible values.
Example with an int converter:
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f'Post {post_id}'
url_for('show_post', post_id=42) # '/post/42'
Adding Query Parameters with url_for()
Arguments that do not belong to the route pattern become query parameters:
@app.route('/search')
def search():
# Imagine we read request.args['q'] and request.args['page']
...
# In Python:
url_for('search', q='flask', page=2)
# Result: '/search?q=flask&page=2'
Notes:
Flask automatically URL-encodes special characters (spaces, &, etc.).
Combine path variables and query parameters in one call.
@app.route('/user/<username>/posts')
def user_posts(username):
...
url_for('user_posts', username='Junzhe', page=3, sort='latest')
# '/user/Junzhe/posts?page=3&sort=latest'
Using url_for() in Templates (Jinja2)
In Jinja2 templates, use url_for exactly like in Python:
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('profile', username='Junzhe') }}">
View profile
</a>
<a href="{{ url_for('search', q='flask', page=1) }}">
Search for Flask
</a>
Advantages:
Templates react automatically to route changes.
Mixing logic (URL building) and HTML stays clean.
URL Building for Static Files
Flask automatically serves static files from the static/ directory at the /static URL prefix.
Use url_for('static', ...) to generate links to static resources:
<!-- In a Jinja2 template -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" />
Why not write /static/css/style.css directly?
URL prefix can be changed in app configuration.
Caches and versioning strategies sometimes modify static URLs.
url_for ensures all links stay correct.
Custom Endpoint Names
You can override the default endpoint name (function name) using the endpoint argument in route():
@app.route('/dashboard', endpoint='user_dashboard')
def show_dashboard():
return 'Dashboard'
# URL building:
url_for('user_dashboard') # '/dashboard'
URL Building with Blueprints
When using blueprints, endpoint names automatically get a prefix: 'blueprint_name.view_function'.
from flask import Blueprint, url_for
bp = Blueprint('admin', __name__, url_prefix='/admin')
@bp.route('/users')
def user_list():
...
app.register_blueprint(bp)
# Building URL:
url_for('admin.user_list') # '/admin/users'
In templates, you use the same endpoint:
<a href="{{ url_for('admin.user_list') }}">Admin users</a>
Blueprint-aware URL building keeps your routes nicely namespaced.
Building Absolute URLs (_external and _scheme)
By default, url_for() returns a relative path (e.g. /user/Junzhe).
To build an absolute URL (including domain), use _external=True:
with app.test_request_context():
url_for('profile', username='Junzhe', _external=True)
# e.g. 'http://localhost/user/Junzhe'
To control the scheme (HTTP/HTTPS), use _scheme:
with app.test_request_context():
url_for('profile', username='Junzhe',
_external=True, _scheme='https')
# 'https://localhost/user/Junzhe'
Absolute URLs are useful for:
email links
redirects to your site from external systems
API callbacks and webhooks
Using url_for() with redirect()
Flask’s redirect() often works together with url_for():
from flask import redirect, url_for
@app.route('/old-home')
def old_home():
# Redirect to the new route
return redirect(url_for('index'))
@app.route('/')
def index():
return 'New homepage'
This pattern allows you to:
migrate URLs gradually
avoid broken links
implement login redirects, logout redirects, etc.
Flask HTTP Methods
What Are HTTP Methods in Flask?
HTTP methods define the action a client wants to perform when sending a request to a server.
Flask allows each route to explicitly define which HTTP methods it accepts.
If a request uses a method not allowed by the route, Flask returns 405 Method Not Allowed .
Defining Allowed Methods in @app.route()
Use the methods parameter to specify allowed HTTP methods:
@app.route('/submit', methods=['POST'])
def submit():
return 'Form submitted!'
If methods is not specified, Flask defaults to GET .
Multiple allowed methods:
@app.route('/user', methods=['GET', 'POST'])
def user():
if request.method == 'POST':
return 'Posted!'
return 'User page'
GET Method
GET requests retrieve data from the server.
Typical use cases:
loading pages
fetching API resources
reading query parameters
Example:
@app.route('/hello')
def hello():
return 'Hello, World!'
GET requests can include query parameters such as ?page=2 via request.args.
POST Method
POST is used to submit data, often from forms or JSON clients.
Reads data via request.form or request.json.
Example using HTML form data:
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
return f'Logged in as {username}'
JSON POST example:
@app.route('/api/data', methods=['POST'])
def api_data():
data = request.json
return {'received': data}
POST is not idempotent — each call may create new data.
PUT Method
PUT replaces an entire resource.
Often used in REST APIs.
@app.route('/profile/<int:id>', methods=['PUT'])
def update_profile(id):
data = request.json
return {'id': id, 'updated_profile': data}
PUT is idempotent: sending the same request multiple times results in the same final state.
PATCH Method
PATCH updates part of a resource.
Used for partial modifications, such as updating a single field.
@app.route('/profile/<int:id>', methods=['PATCH'])
def patch_profile(id):
changes = request.json
return {'id': id, 'changes': changes}
PATCH is sometimes non-idempotent (depends on implementation).
DELETE Method
DELETE removes a resource.
Example:
@app.route('/item/<int:item_id>', methods=['DELETE'])
def delete_item(item_id):
return {'status': 'deleted', 'id': item_id}
DELETE is idempotent — deleting the same resource twice yields the same result.
HEAD Method
HEAD returns headers only (no body).
Used for:
checking if a resource exists
metadata queries
lightweight monitoring
Flask automatically provides a HEAD method for every GET route .
No extra code needed:
@app.route('/info')
def info():
return 'data'
HEAD /info → same headers, empty body.
OPTIONS Method
OPTIONS asks the server which HTTP methods are supported.
Flask automatically provides an OPTIONS response for every route.
Useful for:
CORS preflight checks
API clients
Example client request:
OPTIONS /user
Handling Different Methods Inside a Single View
You can check the incoming method using request.method:
@app.route('/settings', methods=['GET', 'POST'])
def settings():
if request.method == 'POST':
return 'Settings updated'
return 'Settings page'
Useful for pages that show a form (GET) and process it (POST).
Using @app.get, @app.post, @app.put (Shorthand Decorators)
Starting from Flask 2.0, convenient decorators were added:
@app.get('/hello')
def hello():
return 'GET request'
@app.post('/submit')
def submit():
return 'POST request'
@app.delete('/remove/<int:id>')
def remove(id):
return f'Removed {id}'
Each shortcut decorator is equivalent to @app.route(..., methods=['...']).
Method-Specific Routing Examples
RESTful user API example:
@app.route('/users', methods=['GET'])
def list_users():
return 'List users'
@app.route('/users', methods=['POST'])
def create_user():
return 'Create user'
@app.route('/users/<int:id>', methods=['GET'])
def get_user(id):
return f'Get user {id}'
@app.route('/users/<int:id>', methods=['PUT'])
def update_user(id):
return f'Update user {id}'
@app.route('/users/<int:id>', methods=['DELETE'])
def delete_user(id):
return f'Delete user {id}'
This style is common for building full REST APIs.
405 Method Not Allowed
If a method is not allowed on a route, Flask returns 405 .
Example:
Route: /login only accepts POST.
Sending GET → Flask responds 405.
Flask also includes an Allow header showing valid methods.
Summary Table of HTTP Methods
Method
Purpose
Idempotent?
GET
Retrieve data
Yes
POST
Create or submit data
No
PUT
Replace entire resource
Yes
PATCH
Modify part of resource
Sometimes
DELETE
Remove resource
Yes
HEAD
Headers only
Yes
OPTIONS
Discover supported methods
Yes
Flask Templates
What Are Templates in Flask?
Templates in Flask allow you to generate dynamic HTML by mixing static markup with dynamic Python data.
Flask uses the Jinja2 templating engine, which supports:
variables
control structures (if, for)
template inheritance
filters
macros and blocks
Templates help separate:
application logic (Python)
presentation logic (HTML)
By default, Flask looks for template files inside the templates/ folder.
Setting Up the Templates Folder
project/
app.py
templates/
index.html
about.html
Flask will automatically search templates/ for rendering files.
Rendering Templates with render_template()
Import and use render_template:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
Flask finds templates/index.html automatically.
Passing Data to Templates
Pass variables as keyword arguments:
@app.route('/user/<name>')
def user_profile(name):
return render_template('profile.html', username=name)
Inside profile.html:
<h1>Hello, {{ username }}!</h1>
Flask automatically injects variables into the Jinja2 context.
Jinja2 Template Syntax: Variables
Variables are written using double curly braces:
<p>Welcome, {{ user }}!</p>
Objects and attributes:
<p>Your score: {{ stats.score }}</p>
Filter usage:
<p>Uppercased: {{ username|upper }}</p>
Jinja2 Control Structures
Use {% ... %} for logic blocks.
If statement:
{% if is_admin %}
<p>Welcome, admin!</p>
{% else %}
<p>Welcome, user!</p>
{% endif %}
For loop:
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
Template Inheritance
Create a base template to avoid repeating common layout:
<!-- templates/base.html -->
<html>
<head>
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
<header>Header content</header>
{% block content %}{% endblock %}
<footer>Footer</footer>
</body>
</html>
Extend it in other templates:
{% extends 'base.html' %}
{% block title %}Homepage{% endblock %}
{% block content %}
<h1>Welcome to the homepage</h1>
{% endblock %}
Template inheritance keeps your project DRY and organized.
Using Layout Blocks
Common blocks include:
block title
block content
block scripts
Block example:
{% block scripts %}
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
{% endblock %}
Using Static Files Inside Templates
Static files live in the static/ folder:
project/
static/
css/
js/
images/
Use url_for() to link them:
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
<img src="{{ url_for('static', filename='images/logo.png') }}" />
Never hardcode /static/... manually.
Template Filters
Filters modify values inside templates.
Common filters:
upper – uppercase text
lower – lowercase
length – length of iterable
safe – mark HTML as safe
join – join lists
Example:
{{ ['A', 'B', 'C']|join(', ') }}
You can also create custom filters.
Including Templates
You can include reusable HTML fragments:
{% include 'sidebar.html' %}
Useful for:
navigation menus
headers/footers when not using inheritance
modular UI components
Escaping and Safe Rendering
Jinja2 autoescapes HTML to prevent XSS attacks.
HTML rendered from variables is escaped:
{{ user_input }}
To render raw HTML, use |safe:
{{ html_content|safe }}
Use safe with caution.
Macros in Templates
Macros let you create reusable template functions:
{% macro button(text) %}
<button>{{ text }}</button>
{% endmacro %}
Call the macro:
{{ button('Click me') }}
Macros improve reusability inside templates.
Template Comments
Comment inside Jinja2 with:
{# This is a comment #}
Comments do not appear in the output HTML.
Using url_for() in Templates
url_for is available directly in every template.
Example with arguments:
<a href="{{ url_for('profile', username='Junzhe') }}">
View Profile
</a>
Custom Template Folders
You can override the default folder:
app = Flask(__name__, template_folder='views')
This allows project-specific organization.
Rendering Non-HTML Templates
Templates can render any text-based output:
Example rendering XML:
@app.route('/sitemap')
def sitemap():
return render_template('sitemap.xml')
Common Mistakes and Best Practices
Always use url_for() for links to avoid broken URLs.
Use template inheritance for consistent layouts.
Keep templates clean and move logic to Python whenever possible.
Store reusable HTML in partials and include them.
Use custom filters/macros for repeated formatting patterns.
Flask Static Files
What Are Static Files in Flask?
Static files are files that do not change dynamically and are served directly to the client.
These include:
CSS files
JavaScript files
Images (PNG, JPG, SVG)
Fonts (TTF, WOFF)
Static HTML assets
Flask automatically serves static files located inside the static/ directory.
Default Static Folder Structure
This is the recommended project layout:
project/
app.py
static/
css/
style.css
js/
app.js
images/
logo.png
templates/
index.html
Flask automatically maps /static/... to the static/ directory.
Using url_for() to Link Static Files
Never hardcode /static/ paths manually — always use url_for.
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo" />
url_for('static', filename=...) ensures URLs stay correct even if the static folder or configuration changes.
Serving Static Files Automatically
Flask automatically registers a route:
/static/<path:filename>
This handles requests like:
/static/css/style.css
/static/js/app.js
/static/images/logo.png
No extra code is needed to serve static files.
Using Static Files in Templates
Typical HTML template including CSS and JS:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
</head>
<body>
<h1>Welcome</h1>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>
Static files help define the styling and behavior of web pages.
Customizing the Static Folder Location
You can change the default static/ folder:
app = Flask(__name__, static_folder='public_static')
Custom static URL path:
app = Flask(__name__, static_url_path='/assets')
Now static files live at:
/assets/...
Example combining both:
app = Flask(
__name__,
static_folder='public',
static_url_path='/static-files'
)
This provides full control over deployment structure.
Serving Static Files Manually (Advanced)
Flask provides send_from_directory() for custom static folders.
from flask import send_from_directory
@app.route('/media/<path:filename>')
def media(filename):
return send_from_directory('uploads', filename)
Use cases:
uploaded user files
exported reports
protected/static-like content
Static Files Versioning (Cache-Busting)
Browsers cache static files aggressively.
To avoid stale caches, use url_for() with a timestamp or version:
<script src="{{ url_for('static', filename='js/app.js', v=1) }}"></script>
Resulting URL:
/static/js/app.js?v=1
You can automate this with hashed filenames during deployment.
Using Static Files in Blueprints
Blueprints can have their own static folders:
admin_bp = Blueprint(
'admin',
__name__,
static_folder='static',
static_url_path='/admin-static'
)
Blueprint static file example:
<img src="{{ url_for('admin.static', filename='images/icon.png') }}" />
This allows module-based static asset organization.
Static vs Template Files (Important Distinction)
templates/ :
Dynamic content
Processed by Jinja2
Rendered with variables
static/ :
Unprocessed raw files
Delivered directly to the browser
Cannot contain Jinja2 code
You should place:
HTML → templates/
CSS/JS/Images → static/
Favicon Handling
Place your favicon inside static/:
static/favicon.ico
Link in base.html:
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" />
Browsers automatically request /favicon.ico, so some apps map it directly:
@app.route('/favicon.ico')
def favicon():
return send_from_directory('static', 'favicon.ico')
Flask Request Object
What Is the Flask Request Object?
The request object in Flask provides access to all data sent by the client (browser, API client, etc.).
It includes:
HTTP method
Form data
JSON payload
Headers
Cookies
URL parameters
Uploaded files
Client IP
Import it from Flask:
from flask import request
The request object is context-aware, meaning it only exists during an active request.
Accessing HTTP Methods
Check the method used by the client:
@app.route('/submit', methods=['GET', 'POST'])
def submit():
return f"Method used: {request.method}"
Common values:
GET
POST
PUT
PATCH
DELETE
Reading Query Parameters (request.args)
Query parameters come from the URL, e.g. ?page=3&sort=asc.
Use request.args to access them:
@app.route('/search')
def search():
query = request.args.get('q')
page = request.args.get('page', default=1, type=int)
return f"Search: {query}, Page: {page}"
request.args is a MultiDict — it supports duplicate keys.
Reading Form Data (request.form)
Form data comes from POST requests with application/x-www-form-urlencoded or multipart/form-data.
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form.get('password')
return f"User: {username}"
request.form is also a MultiDict.
Reading JSON Data (request.json)
For clients sending JSON (application/json):
@app.route('/api/data', methods=['POST'])
def api_data():
data = request.json # or request.get_json()
return {'received': data}
Good for RESTful APIs.
Accessing Form and JSON Together (request.values)
request.values merges:
request.form
request.args
@app.route('/combined')
def combined():
return request.values.get('key')
Useful when a value may come from GET or POST.
Accessing Headers (request.headers)
Retrieve client or browser headers:
@app.route('/agent')
def agent():
ua = request.headers.get('User-Agent')
return f"Your browser: {ua}"
Common headers:
Content-Type
User-Agent
Accept
Authorization
Accessing Cookies (request.cookies)
Client cookies are handled via request.cookies:
@app.route('/show-cookie')
def show_cookie():
token = request.cookies.get('auth_token')
return f"Cookie: {token}"
Cookies can be set in responses.
Handling File Uploads (request.files)
To handle uploaded files from forms:
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['photo']
file.save(f"uploads/{file.filename}")
return 'Uploaded!'
Form requires enctype="multipart/form-data".
Reading Route Variables (request.view_args)
For URLs with variable rules like:
@app.route('/user/<username>')
def profile(username):
print(request.view_args) # {'username': 'Junzhe'}
return username
view_args contains the path parameters.
Getting the Full Request URL
You can access:
Full URL with request.url
Base URL with request.base_url
Host URL with request.host_url
URL without query with request.path
@app.route('/info')
def info():
return {
"url": request.url,
"path": request.path,
"base": request.base_url,
}
Accessing Client IP Address
ip = request.remote_addr
If behind a proxy, use:
ip = request.headers.get('X-Forwarded-For', request.remote_addr)
Detecting AJAX Requests
Traditional AJAX requests include a header:
is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
Modern clients often use fetch() without this header, so custom headers may be needed.
Checking MIME Types
Access incoming request content type:
@app.route('/type', methods=['POST'])
def content_type():
return request.content_type
Common MIME types:
application/json
application/x-www-form-urlencoded
multipart/form-data
Working with Request Data Safely
Use .get() when values may be missing.
Prefer request.get_json() over request.json when needing error handling.
Validate all user input (form, JSON, files).
Never trust request.headers from the client.
Summary Table of Common Attributes
Attribute
Description
request.method
HTTP method
request.args
Query parameters
request.form
Form POST data
request.json
JSON payload
request.files
Uploaded files
request.headers
HTTP headers
request.cookies
Client cookies
request.view_args
Route variables
request.url
Full URL
request.remote_addr
Client IP
What Does It Mean to Send Form Data to a Template?
After a user submits a form (usually via POST), the server receives the data inside request.form.
To display this submitted data back to the user, you pass it from the route into render_template().
Flask makes this very easy thanks to the request object and Jinja2 template rendering.
Typical Workflow for Sending Form Data
User fills a form in an HTML template.
Browser sends a POST request.
Flask receives the form data via request.form.
Flask passes this data to another template using render_template().
Template displays the submitted values.
Creating a Simple Form in a Template
Create a form inside templates/form.html:
<h1>Enter Your Info</h1>
<form action="{{ url_for('result') }}" method="POST">
<label>Name:</label>
<input type="text" name="username" />
<br/>
<label>Age:</label>
<input type="number" name="age" />
<br/>
<button type="submit">Submit</button>
</form>
The form posts data to the route named result.
Receiving Form Data in Flask
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/form')
def form():
return render_template('form.html')
@app.route('/result', methods=['POST'])
def result():
username = request.form.get('username')
age = request.form.get('age')
return render_template('result.html', username=username, age=age)
Explanation:
request.form contains all submitted form fields.
Values are passed into result.html template via keyword arguments.
Displaying Form Data in the Template
<h1>Submitted Data</h1>
<p>Name: {{ username }}</p>
<p>Age: {{ age }}</p>
The variables {{ username }} and {{ age }} come from render_template().
Sending Multiple Form Fields
Flask can pass many fields simply by including them in the context:
return render_template(
'profile.html',
name=request.form.get('name'),
email=request.form.get('email'),
address=request.form.get('address')
)
A clean approach is sending all form values at once:
return render_template('profile.html', form=request.form)
Then in the template:
<p>Name: {{ form['name'] }}</p>
<p>Email: {{ form['email'] }}</p>
<p>Address: {{ form.get('address') }}</p>
Handling Empty or Missing Form Fields
Use get() to avoid errors:
username = request.form.get('username', 'Unknown')
In templates, safely check values:
{% if username %}
<p>Hello, {{ username }}!</p>
{% else %}
<p>No username provided.</p>
{% endif %}
Sending Form Data to Another Template After Validation
@app.route('/signup', methods=['POST'])
def signup():
username = request.form.get('username')
if not username:
error = "Username is required!"
return render_template('signup.html', error=error)
return render_template('welcome.html', username=username)
Templates:
<!-- signup.html -->
{% if error %}
<p style="color: red">{{ error }}</p>
{% endif %}
<!-- welcome.html -->
<h1>Welcome, {{ username }}!</h1>
Sending Form Data to Template Using Jinja2 Loops
If the form contains repeated fields (e.g., checkboxes, multiple selects), Flask stores them with getlist():
choices = request.form.getlist('interests')
return render_template('summary.html', choices=choices)
Template:
<ul>
{% for c in choices %}
<li>{{ c }}</li>
{% endfor %}
</ul>
Good for:
multi-select
checkbox groups
tags/categories
Preserving Form Input After Submission
Useful to retain form values after errors.
In route:
return render_template('signup.html', form=request.form, error=error)
In template:
<input type="text" name="username" value="{{ form.get('username', '') }}" />
This improves usability during validation.
Passing Form Data with Redirect–POST–Redirect Pattern
The common pattern to avoid duplicate submissions:
POST (user submits form)
Redirect
GET (display result)
Example:
from flask import redirect, url_for, session
@app.route('/submit', methods=['POST'])
def submit():
session['form_data'] = request.form.to_dict()
return redirect(url_for('result'))
@app.route('/result')
def result():
data = session.pop('form_data', {})
return render_template('result.html', data=data)
This prevents issues when user refreshes the page after submitting a form.
Flask Cookies
What Are Cookies in Flask?
Cookies are small pieces of data stored on the client ’s browser.
They are sent automatically with every request to the same server, allowing Flask to:
remember user preferences
store session identifiers
track login status
implement analytics
implement "remember me" functionality
In Flask, cookies are accessed through the request object and set using the Response object.
Accessing Cookies (request.cookies)
Retrieve a cookie sent by the client:
@app.route('/show-cookie')
def show_cookie():
username = request.cookies.get('username')
return f"Hello, {username}"
If the cookie doesn’t exist, .get() returns None unless a default is supplied:
username = request.cookies.get('username', 'Guest')
Setting Cookies in Flask
Cookies are set on the response object, not directly in the route return value.
Example:
@app.route('/set-cookie')
def set_cookie():
resp = make_response("Cookie Set!")
resp.set_cookie('username', 'Junzhe')
return resp
Alternatively, using redirect():
@app.route('/login')
def login():
resp = redirect(url_for('dashboard'))
resp.set_cookie('logged_in', 'true')
return resp
Cookie Options (Expiration, Path, Domain)
You can customize how cookies behave using options:
resp.set_cookie(
'username',
'Junzhe',
max_age=60*60*24, # 1 day
path='/',
secure=True,
httponly=True,
samesite='Lax'
)
Common cookie parameters:
Cookie Parameter
Description
max_age
Expiration time in seconds.
expires
Specific expiration date (HTTP-date format).
path
Cookie is sent only for this URL path.
domain
Limits cookie to a specific domain or subdomain.
secure
Cookie is sent only over HTTPS.
httponly
Prevents JavaScript from accessing the cookie.
samesite
Controls cross-site cookie behavior (helps prevent CSRF). Values: "Strict", "Lax", "None".
Security recommendation: always use httponly=True when possible.
Deleting Cookies
To delete a cookie, use delete_cookie() on the response:
@app.route('/logout')
def logout():
resp = make_response("Logged out")
resp.delete_cookie('username')
return resp
This sends the browser an expired cookie, effectively removing it.
Using Cookies to Maintain User Preferences
Example: storing the user’s preferred theme:
@app.route('/set-theme/<theme>')
def set_theme(theme):
resp = redirect(url_for('homepage'))
resp.set_cookie('theme', theme, max_age=30*24*60*60)
return resp
Using the stored theme in templates:
@app.route('/')
def homepage():
theme = request.cookies.get('theme', 'light')
return render_template('index.html', theme=theme)
Inside template:
<body class="{{ theme }}">
Storing JSON or Large Data in Cookies
Cookies can only store small strings .
If you need to store structured data, serialize it:
import json
data = json.dumps({'theme': 'dark', 'fontsize': 16})
resp.set_cookie('settings', data)
Retrieve and parse:
settings = json.loads(request.cookies.get('settings', '{}'))
Avoid storing sensitive data — cookies are visible to users.
Cookies vs Flask Sessions
Flask sessions use cookies internally, but with cryptographic signing.
Cookies:
Developer manually manages key-value pairs.
Values are exposed to the user.
Sessions:
Automatically signed using SECRET_KEY.
More secure.
Simpler for storing user-specific data.
Rule of thumb:
Use cookies for simple preferences (theme, language).
Use sessions for authentication or private data.
Checking If a Cookie Exists
@app.route('/check')
def check():
if 'username' in request.cookies:
return 'Cookie exists'
return 'No cookie found'
Useful for login checks, custom preferences, and onboarding flows.
Security Best Practices for Cookies
Enable HttpOnly to prevent JavaScript access:
resp.set_cookie('token', 'abc', httponly=True)
Use Secure flag to allow sending cookies only over HTTPS:
resp.set_cookie('token', 'abc', secure=True)
Use SameSite to mitigate CSRF:
resp.set_cookie('token', 'abc', samesite='Strict')
Never store sensitive info (passwords, tokens, personal data).
Limit cookie scope using path and domain.
Consider Flask sessions for signed cookie storage.
Flask Sessions
What Are Sessions in Flask?
A session in Flask is a secure way to store user-specific data across multiple requests.
Unlike cookies, which store plain text data, Flask sessions store data in a cookie that is:
signed (cannot be modified by the user)
tamper-proof (protected by secret key)
persistent across requests
Sessions are ideal for:
login states
shopping carts
user preferences
multi-step forms
storing temporary user data
Flask stores session data in a cryptographically signed cookie using SECRET_KEY.
Enabling Sessions (SECRET_KEY)
To use sessions, you must define a SECRET_KEY in your Flask app:
from flask import Flask
app = Flask(__name__)
app.secret_key = 'super-secret-key' # Use a strong random value in production
Without a secret key, Flask cannot sign session cookies.
For production, generate a secure key:
import secrets
secrets.token_hex(32)
Storing Data in Session
Sessions behave like a Python dictionary:
from flask import session
@app.route('/login')
def login():
session['user'] = 'Junzhe'
session['theme'] = 'dark'
return 'Session data stored!'
Data persists across requests as long as the session cookie remains valid.
Retrieving Session Data
Use dictionary-style access:
@app.route('/dashboard')
def dashboard():
user = session.get('user', 'Guest')
return f"Welcome, {user}!"
Always use session.get() to avoid KeyError.
Checking If Session Keys Exist
Check login state example:
@app.route('/check')
def check():
if 'user' in session:
return 'User logged in'
return 'User not logged in'
Removing Items from Session
@app.route('/logout')
def logout():
session.pop('user', None)
return 'Logged out!'
Clear entire session:
session.clear()
Session Lifetime (Permanent Sessions)
By default, Flask sessions last until the browser is closed.
Enable permanent session:
from datetime import timedelta
@app.route('/remember')
def remember():
session.permanent = True
app.permanent_session_lifetime = timedelta(days=7)
session['user'] = 'Junzhe'
return 'Permanent session stored for 7 days!'
Permanent sessions store expiration time in the cookie.
How Flask Stores Sessions Internally
Flask stores session data inside a cookie in a structure like:
".eJyrVkrLz1eyUkpKLFKqBQB3DAwN.yh4Y8R5pp0pH2u9dTJAyWF6TA"
It includes your session data (serialized) and a secure signature
The user cannot modify session data without breaking the signature, making sessions safe.
Using Sessions with Forms (Common Use Case)
Store submitted form data in session:
@app.route('/submit', methods=['POST'])
def submit():
session['form_data'] = request.form.to_dict()
return redirect(url_for('show_form'))
Display in template:
@app.route('/show')
def show_form():
data = session.get('form_data', {})
return render_template('show.html', data=data)
This pattern supports multi-step workflows.
Using Sessions for Login Systems
Simple authentication example:
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
if username == 'admin':
session['user'] = username
return redirect(url_for('dashboard'))
return 'Invalid login'
@app.route('/dashboard')
def dashboard():
if 'user' not in session:
return redirect(url_for('login'))
return f"Welcome, {session['user']}!"
Sessions make it easy to remember authenticated users.
Securing Sessions
Flask sessions are secure only if SECRET_KEY is strong.
Recommended settings:
resp.set_cookie(
'session',
httponly=True,
secure=True,
samesite='Lax'
)
When to Use Sessions vs Cookies
Use sessions when:
you need secure, tamper-proof data
you store login information
you store multi-step form progress
data should not be readable by users
Use cookies when:
storing simple preferences (theme, language)
data does not need to be protected
values are small
Summary of Session Methods
Operation
Code Example
Set session value
session['key'] = value
Get session value
session.get('key')
Check existence
'key' in session
Remove value
session.pop('key')
Clear all
session.clear()
Make session permanent
session.permanent = True
Flask Redirect & Errors
Using redirect() with url_for()
The most common way to redirect:
from flask import redirect, url_for
@app.route('/old')
def old():
return redirect(url_for('new'))
@app.route('/new')
def new():
return "You have been redirected!"
redirect() returns an HTTP 302 response by default.
url_for() builds the correct URL for the target route.
Redirecting to External URLs
You can also redirect to websites outside your application:
@app.route('/github')
def github():
return redirect("https://github.com")
Useful for linking to external services, documentation, or OAuth providers.
Using Different Redirect Status Codes
Default: 302 Found (temporary redirect).
Permanent redirect (301):
return redirect(url_for('new'), code=301)
Other redirect codes:
303 — See Other
307 — Temporary redirect (same method)
308 — Permanent redirect (same method)
Flask supports all of them via the code parameter.
Redirect After Form Submission (POST → Redirect → GET)
This prevents duplicate form submissions.
Example:
@app.route('/submit', methods=['POST'])
def submit():
# Process form...
return redirect(url_for('thank_you'))
@app.route('/thank-you')
def thank_you():
return "Form submitted!"
This is the recommended pattern for all POST requests.
Basic Error Handling Using abort()
Flask provides abort() to trigger HTTP errors manually:
from flask import abort
@app.route('/secret')
def secret():
abort(403) # Forbidden
Common error codes:
400 — Bad Request
401 — Unauthorized
403 — Forbidden
404 — Not Found
500 — Server Error
abort() stops execution immediately and raises an error response.
Creating Custom Error Pages
Use the @app.errorhandler decorator:
@app.errorhandler(404)
def not_found(e):
return render_template('404.html'), 404
Custom templates improve user experience.
Example 404.html:
<h1>Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
Handling 500 Internal Server Errors
A custom global error handler:
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
You can log the error for debugging:
import logging
logging.error(e)
Custom 500 pages are vital for production apps.
Handling Multiple Errors in One Place
You can register handlers for multiple codes:
@app.errorhandler(403)
@app.errorhandler(401)
@app.errorhandler(400)
def handle_auth_errors(e):
return render_template('auth_error.html', error=e), e.code
Useful for grouping similar errors.
Raising Custom Error Messages
You can provide a custom message:
abort(404, description="User not found")
Render description inside templates:
<p>{{ error.description }}</p>
Redirecting Instead of Showing an Error
Sometimes it’s better to redirect the user:
@app.route('/admin')
def admin():
if not session.get('is_admin'):
return redirect(url_for('login'))
return "Admin page"
This avoids exposing internal logic to users.
Error Pages with Jinja2 and Dynamic Data
You can pass variables to error templates:
@app.errorhandler(404)
def not_found(e):
path = request.path
return render_template('404.html', path=path), 404
Example template:
<p>The path <b>{{ path }}</b> does not exist.</p>
Using Blueprints for Error Handling
Blueprint-specific error handlers:
@bp.errorhandler(404)
def page_not_found(e):
return render_template('errors/404.html'), 404
Allows modular error management.
Summary of Redirect and Error Functions
Function
Purpose
redirect()
Send browser to another URL
url_for()
Build a route URL
abort()
Raise an HTTP error
@app.errorhandler()
Custom error page for an HTTP code
Flask Message Flashing
What Is Message Flashing in Flask?
Message flashing is a feature in Flask that allows you to store short messages temporarily and display them to the user on the next page.
Common use cases:
success messages (e.g., “Profile updated successfully!”)
error messages (e.g., “Invalid login credentials.”)
warnings / notices
confirmation messages
Flash messages persist only for one request (stored via session cookies).
Flash messages rely on Flask sessions → requires SECRET_KEY.
Enabling Flash Messages
Make sure you set a secret key in your Flask app:
from flask import Flask
app = Flask(__name__)
app.secret_key = 'my-secret-key'
Without a secret key, flash messages will not work because flashing uses sessions internally.
Flashing a Message
Use the flash() function:
from flask import flash
@app.route('/login', methods=['POST'])
def login():
flash("Login successful!")
return redirect(url_for('dashboard'))
You can call flash() multiple times to store multiple messages.
Displaying Flash Messages in Templates
In your template (usually base.html):
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for msg in messages %}
<li>{{ msg }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
get_flashed_messages() retrieves all flashed messages and clears them afterward.
Flashing Messages with Categories
Messages can be grouped by categories (e.g., error, success, warning):
flash("Logged in successfully!", "success")
flash("Invalid password!", "error")
Retrieve categories in template:
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, msg in messages %}
<li class="{{ category }}">{{ msg }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
Use categories for UI styling (CSS classes):
.success { color: green; }
.error { color: red; }
.warning { color: orange; }
Redirect–Flash–Render Pattern
Flash messages are commonly used after redirecting:
@app.route('/update', methods=['POST'])
def update():
flash("Profile updated!", "success")
return redirect(url_for('settings'))
Then the settings page template shows the message.
This prevents message loss during navigation.
Using Flash Messages With Forms
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
if username != "admin":
flash("Incorrect username!", "error")
return redirect(url_for('login_form'))
flash("Welcome admin!", "success")
return redirect(url_for('dashboard'))
Messages guide users through form errors or success events.
Storing Multiple Flash Messages
You can flash multiple messages before redirecting:
flash("Settings saved!", "success")
flash("New updates applied.", "info")
flash("Remember to restart the app.", "warning")
Templates will automatically list all of them.
Using Flash Message Filters
You can filter categories:
{% with errors = get_flashed_messages(category_filter=["error"]) %}
{% if errors %}
<ul class="errors">
{% for err in errors %}
<li>{{ err }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
Useful when different parts of the page show different types of messages.
Displaying Flash Messages Using Bootstrap
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, msg in messages %}
<div class="alert alert-{{ category }}" role="alert">
{{ msg }}
</div>
{% endfor %}
{% endwith %}
Use categories matching Bootstrap:
success
danger
warning
info
Flashing Messages in Blueprints
Flash works normally inside blueprints:
@bp.route('/action')
def action():
flash("Action completed!", "info")
return redirect(url_for('bp.index'))
Messages are shared across the entire app via session.
Advanced: Flashing with Additional Payload
You can attach structured data (rarely needed):
flash(("File uploaded", {"size": "2MB"}), "info")
Template:
{% for category, data in get_flashed_messages(with_categories=true) %}
{% set msg, info = data %}
<p>{{ msg }} ({{ info.size }})</p>
{% endfor %}
Use sparingly—flash messages are meant to be simple.
Clearing All Flash Messages (Rare Case)
Flash messages clear automatically after retrieval.
If needed manually:
session.pop('_flashes', None)
Not recommended unless you know what you're doing.
Flask File Uploading
Common upload use cases:
profile pictures
documents (PDF, Word)
CSV or Excel files for processing
image galleries
audio/video uploads
File uploading requires:
an HTML form with enctype="multipart/form-data"
a file input field
Flask reading request.files
saving the file safely
Flask stores uploaded files in memory or temporary files before you save them.
Creating a File Upload Form
Create templates/upload.html:
<h1>Upload File</h1>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('upload_file') }}">
<input type="file" name="file" /><br/><br/>
<button type="submit">Upload</button>
</form>
Important: Without enctype="multipart/form-data", uploads will not work.
Receiving and Saving Uploaded Files
Basic upload route in Flask:
from flask import request, render_template
import os
UPLOAD_FOLDER = "uploads"
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == "POST":
file = request.files.get('file')
if not file:
return "No file uploaded"
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
file.save(filepath)
return f"Uploaded to {filepath}"
return render_template('upload.html')
request.files contains uploaded files keyed by the filename alias.
The file is saved using file.save().
Ensuring the Upload Directory Exists
Create the folder manually:
project/
app.py
uploads/
templates/
Or create it automatically:
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
Securing File Uploads with secure_filename()
Never trust filenames sent from clients—sanitize them:
from werkzeug.utils import secure_filename
filename = secure_filename(file.filename)
file.save(os.path.join(UPLOAD_FOLDER, filename))
This prevents dangerous names like:
../../../../etc/passwd
secure_filename() removes risky characters and ensures safety.
Restricting Allowed File Types
Define allowed extensions:
ALLOWED = {'png', 'jpg', 'jpeg', 'gif', 'pdf'}
def allowed(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED
Validate before saving:
if not allowed(file.filename):
return "File type not allowed"
Prevents users from uploading unsafe files.
Handling Multiple File Uploads
<input type="file" name="files" multiple />
Flask route:
files = request.files.getlist('files')
for f in files:
f.save(os.path.join(UPLOAD_FOLDER, secure_filename(f.filename)))
Useful for galleries, attachments, etc.
Retrieving Uploaded Files (Serving Them)
Use send_from_directory():
from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
This allows users to access uploaded files safely.
Limiting Maximum File Size
Flask allows setting a maximum upload size:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MB
Requests larger than this will raise a 413 Request Entity Too Large error.
Handling Upload Errors
@app.errorhandler(413)
def too_large(e):
return "File is too large!", 413
Other common upload errors:
no file submitted
empty filename
invalid file type
upload directory missing
Using Flash Messages for Upload Feedback
flash("File uploaded successfully!", "success")
This helps guide users during upload operations.
File Uploading with Blueprints
You can implement uploads inside a blueprint normally:
@bp.route('/upload', methods=['POST'])
def bp_upload():
file = request.files.get('file')
file.save(os.path.join("bp_uploads", secure_filename(file.filename)))
return "Uploaded!"
Useful for modular applications.
Security Best Practices for File Uploads
Always sanitize filenames with secure_filename().
Always validate file types and sizes.
Store uploads outside the project root if possible.
Do NOT execute or trust uploaded files.
Configure proper permissions on the upload directory.
For large uploads, consider background processing (Celery, RQ).
Flask Extensions
What Are Flask Extensions?
Flask extensions are third-party packages that add extra features to Flask without bloating the core framework.
They integrate cleanly with Flask’s ecosystem, following common patterns like:
initializing with the app object
supporting the application factory pattern via init_app()
using Flask configuration keys like SQLALCHEMY_DATABASE_URI, MAIL_SERVER, etc.
Typical features provided by extensions:
database access (ORMs like SQLAlchemy)
form handling & CSRF protection
authentication & user management
email sending
migrations
REST APIs
caching, rate limiting, etc.
Extensions let you compose a “micro” core into a full-featured web framework.
Installing Flask Extensions
Extensions are installed via pip (or similar):
pip install Flask-SQLAlchemy
pip install Flask-WTF
pip install Flask-Login
After installation, import them in your Python code, usually with a flask_* or Flask-* module name:
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from flask_login import LoginManager
Always check the extension’s documentation for exact install and import names.
Commonly Used Flask Extensions (Overview)
Some popular extensions in the Flask ecosystem:
Extension
Purpose
Flask-SQLAlchemy
Database ORM support via SQLAlchemy
Flask-Migrate
Database migrations (Alembic integration)
Flask-WTF
Form handling & CSRF protection
Flask-Login
User session management & authentication
Flask-Mail
Email sending via SMTP
Flask-RESTful
REST API helpers & resources
Flask-Caching
Caching responses & computations
Flask-Limiter
Rate limiting (throttling requests)
You can mix and match extensions depending on your project’s needs.
Basic Extension Initialization (Single-File App)
Many extensions are initialized directly with the Flask app instance:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
Here:
configuration is set on app.config
the extension reads necessary config values when initialized
This style is common for small applications or tutorials.
Application Factory Pattern and init_app()
Larger apps often use the application factory pattern:
# extensions.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# app.py
from flask import Flask
from extensions import db
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db.init_app(app)
return app
Pattern:
create extension instances globally (no app yet)
inside create_app(), call extension.init_app(app)
Benefits:
supports multiple apps with same extension
plays nicely with testing and configuration variations
Example: Using Flask-SQLAlchemy
Configuration & model definition:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
return app
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100))
Usage inside a request context:
app = create_app()
with app.app_context():
db.create_all()
Flask-SQLAlchemy simplifies DB integration while still using full SQLAlchemy power.
Example: Flask-WTF (Forms & CSRF)
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.secret_key = 'change-me' # needed for CSRF & session
Define a form:
class NameForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
Use the form in a route:
@app.route('/hello', methods=['GET', 'POST'])
def hello():
form = NameForm()
if form.validate_on_submit():
return f"Hello, {form.name.data}!"
return render_template('hello.html', form=form)
In template:
<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}<br/>
{{ form.submit() }}
</form>
Flask-WTF integrates CSRF protection, validation, and HTML rendering helpers.
Example: Flask-Login (User Sessions)
from flask_login import LoginManager, UserMixin, login_user, login_required, current_user
login_manager = LoginManager()
def create_app():
app = Flask(__name__)
app.secret_key = 'secret'
login_manager.init_app(app)
return app
Define a user model (simplified):
class User(UserMixin):
def __init__(self, id):
self.id = id
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
Using login-required route:
@app.route('/login')
def login():
user = User(id="junzhe")
login_user(user)
return "Logged in!"
@app.route('/secret')
@login_required
def secret():
return f"Hello, {current_user.id}"
Flask-Login abstracts away session logic and authentication helpers.
Configuration Patterns for Extensions
Most extensions read settings from app.config keys:
SQLALCHEMY_DATABASE_URI for Flask-SQLAlchemy
MAIL_SERVER, MAIL_PORT, MAIL_USERNAME for Flask-Mail
CACHE_TYPE for Flask-Caching
Example centralized config:
class Config:
SQLALCHEMY_DATABASE_URI = 'sqlite:///app.db'
MAIL_SERVER = 'smtp.example.com'
MAIL_PORT = 587
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
# init extensions here...
return app
Storing config in a class or separate file keeps extensions consistent and maintainable.
Finding and Evaluating Flask Extensions
Where to look:
PyPI (search for Flask-*)
Flask extension registry (in official docs)
GitHub repositories and stars/issues
When choosing an extension, consider:
maintenance status (recent commits, open issues)
documentation quality
community usage/popularity
compatibility with your Flask version
If an extension looks abandoned, be careful about using it in production.
Writing Your Own Simple Extension (Overview)
Custom extensions usually follow a pattern:
class MyExtension:
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
# setup logic, read app.config, register handlers, etc.
app.my_ext = self
Usage:
my_ext = MyExtension()
def create_app():
app = Flask(__name__)
my_ext.init_app(app)
return app
This pattern mirrors existing extensions, so your own tools integrate nicely with Flask.
Flask Mail
What Is Flask-Mail?
Flask-Mail is an extension that makes it easy to send emails from your Flask application using SMTP.
It supports:
plain text and HTML emails
attachments
multiple recipients
CC/BCC
authentication for SMTP servers
Flask-Mail is suitable for:
contact forms
registration confirmation
password reset flows
reporting errors or notifications
For high-volume email sending, consider integrating Celery or Flask-Mailman.
Installing Flask-Mail
pip install Flask-Mail
Import and prepare the extension:
from flask_mail import Mail, Message
mail = Mail()
Basic Configuration
Add your SMTP configuration:
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'your-email@gmail.com'
app.config['MAIL_PASSWORD'] = 'your-password'
mail.init_app(app)
Common mail settings:
MAIL_SERVER
MAIL_PORT
MAIL_USE_TLS / MAIL_USE_SSL
MAIL_USERNAME
MAIL_PASSWORD
MAIL_DEFAULT_SENDER
Use environment variables in production instead of hardcoding passwords.
Sending a Basic Email
Sending a simple text email:
@app.route('/send')
def send_email():
msg = Message(
subject='Hello from Flask!',
recipients=['user@example.com'],
body='This is a test email.'
)
mail.send(msg)
return "Email sent!"
Flask-Mail handles the SMTP communication automatically.
Sending HTML Emails
HTML content can be added using the html parameter:
msg = Message(
subject='Welcome!',
recipients=['user@example.com'],
html='<h1>Welcome to our site!</h1><p>Thank you for joining.</p>'
)
mail.send(msg)
You can combine plain text and HTML for email clients that do not support HTML.
Sending Emails Using Templates
Render a Flask template and send it as HTML:
from flask import render_template
@app.route('/welcome')
def welcome():
msg = Message("Hello!",
recipients=["user@example.com"])
msg.html = render_template('email_welcome.html', username='Junzhe')
mail.send(msg)
return "Email sent!"
Example templates/email_welcome.html:
<h2>Hello {{ username }}!</h2>
<p>We are happy to have you here.</p>
Sending Emails with Attachments
Attach a file using attach():
msg = Message('Your Invoice', recipients=['user@example.com'])
msg.body = 'Please see the attached invoice.'
with app.open_resource('invoice.pdf') as fp:
msg.attach('invoice.pdf', 'application/pdf', fp.read())
mail.send(msg)
Useful for reports, invoices, documents, logs, etc.
Sending to Multiple Recipients
You may send to one or many:
msg = Message('Announcement',
recipients=['a@a.com', 'b@b.com'],
cc=['c@c.com'],
bcc=['d@d.com'])
Recipient lists must be Python lists or tuples.
Default Sender
app.config['MAIL_DEFAULT_SENDER'] = 'admin@example.com'
Then each message can omit sender unless overridden.
Asynchronous Email Sending
Sending emails can be slow — offload to a thread or Celery.
Using Python threading:
import threading
def send_async(app, msg):
with app.app_context():
mail.send(msg)
@app.route('/async')
def send_async_email():
msg = Message("Hello", recipients=["user@example.com"])
thread = threading.Thread(target=send_async, args=(app, msg))
thread.start()
return "Email being sent asynchronously."
For heavy usage, prefer Celery workers.
Error Handling
SMTP failures may occur, so wrap sending in try/except:
try:
mail.send(msg)
except Exception as e:
return f"Failed to send email: {e}"
Common errors:
invalid SMTP credentials
port blocked
Google blocking “less secure app access”
Check logs if messages don’t send.
Flask-Mail vs Other Mail Solutions
Flask-Mail : simple, easy SMTP integration.
Flask-Mailman : actively maintained modern replacement, Django-like.
External mail APIs:
SendGrid
Mailgun
Amazon SES
API-based mail sending is usually more reliable and scalable.
Flask SQLite
Introduction to SQLite in Flask
SQLite is a lightweight, file-based relational database that requires no server installation.
Flask works very well with SQLite because:
SQLite databases are just files (easy to manage)
perfect for small/medium applications
no extra dependencies are needed
ideal for quick prototypes, student projects, or small production apps
SQLite integrates with Flask in two common ways:
directly using Python’s built-in sqlite3 module
using Flask-SQLAlchemy ORM (recommended for larger apps)
Using SQLite with Python’s sqlite3 Module
This approach uses SQL directly and has zero dependencies:
import sqlite3
def get_db():
conn = sqlite3.connect("database.db")
conn.row_factory = sqlite3.Row # allows dict-like access
return conn
Making a simple Flask route that queries the database:
@app.route('/users')
def show_users():
db = get_db()
cursor = db.execute("SELECT * FROM users")
users = cursor.fetchall()
return { "users": [dict(row) for row in users] }
This is great for small apps or for learning SQL.
Creating an SQLite Database
Create tables using SQL statements:
def init_db():
db = get_db()
db.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
""")
db.commit()
Call this function once to initialize the schema.
Inserting Data into SQLite
@app.route('/add')
def add_user():
db = get_db()
db.execute("INSERT INTO users (name, email) VALUES (?, ?)",
("Junzhe", "junzhe@example.com"))
db.commit()
return "User added!"
Use ? placeholders to avoid SQL injection.
Fetching Data
cursor = db.execute("SELECT * FROM users WHERE id = ?", (1,))
user = cursor.fetchone()
Fetch all records:
cursor = db.execute("SELECT * FROM users")
users = cursor.fetchall()
Using sqlite3.Row allows:
user['name']
Using SQLite with Flask Application Context
Best practice: store the DB connection inside g:
from flask import g
def get_db():
if 'db' not in g:
g.db = sqlite3.connect("database.db")
g.db.row_factory = sqlite3.Row
return g.db
Close automatically after each request:
@app.teardown_appcontext
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
This avoids connection leaks.
Using SQLite with Flask-SQLAlchemy (Recommended)
pip install Flask-SQLAlchemy
Basic setup:
from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydb.sqlite3'
db = SQLAlchemy(app)
Create a model:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
email = db.Column(db.String(100), unique=True)
Initialize database:
with app.app_context():
db.create_all()
SQLAlchemy provides:
ORM models
relationships
query builder
migration support
Querying with SQLAlchemy ORM
u = User(name="Junzhe", email="junzhe@example.com")
db.session.add(u)
db.session.commit()
Fetch users:
users = User.query.all()
Filter:
user = User.query.filter_by(email="junzhe@example.com").first()
Delete:
db.session.delete(user)
db.session.commit()
Using Flask-Migrate with SQLite
Migrations help track model changes:
pip install Flask-Migrate
Setup:
from flask_migrate import Migrate
migrate = Migrate(app, db)
Commands:
flask db init
flask db migrate -m "Initial migration"
flask db upgrade
Even though SQLite has limitations, migrations still work for most schema changes.
Best Practices When Using SQLite with Flask
Use SQLAlchemy ORM for large applications.
Use raw SQL (sqlite3 module) for lightweight apps or learning.
Place your SQLite file outside the static/ or templates/ directory.
Never commit SQLite database files for production apps (contains user data).
Use migrations (Flask-Migrate) to track schema changes.
Close DB connections after each request using teardown_appcontext.
For concurrency-heavy apps, consider PostgreSQL or MySQL instead.
Flask Deployment
Introduction to Deploying Flask Applications
Deployment is the process of running your Flask application on a production-ready server so users can access it online.
Flask’s built-in development server (flask run) is not for production because it:
is single-threaded
cannot handle real traffic
lacks security features
Production deployments require:
a WSGI server (Gunicorn, uWSGI, Waitress)
a reverse proxy (NGINX, Apache)
a Linux server (common), virtual environment, and proper configuration
Preparing Your Flask App for Deployment
Typical project structure:
myapp/
app.py
requirements.txt
venv/
static/
templates/
Create requirements.txt:
pip freeze > requirements.txt
Ensure the application exposes a WSGI entry point:
from app import app # "app" must be your Flask instance
Deploying with Gunicorn (Linux/Unix)
pip install gunicorn
Run your Flask app:
gunicorn app:app
Use multiple workers:
gunicorn -w 4 -b 0.0.0.0:8000 app:app
Gunicorn is not exposed directly—you usually put NGINX in front of it.
Deploying with uWSGI (Alternative)
pip install uwsgi
Create a config file uwsgi.ini:
[uwsgi]
module = app:app
master = true
processes = 4
socket = 127.0.0.1:8000
vacuum = true
die-on-term = true
Run:
uwsgi --ini uwsgi.ini
Recommended for servers using NGINX + uWSGI.
Setting Up NGINX as a Reverse Proxy
sudo apt install nginx
Create config:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Reload NGINX:
sudo systemctl restart nginx
NGINX provides security, SSL/TLS support, compression, caching, and request buffering.
Running Flask as a Systemd Service
Create a service file /etc/systemd/system/flask.service:
[Unit]
Description=Gunicorn instance to serve Flask app
After=network.target
[Service]
User=www-data
WorkingDirectory=/home/youruser/myapp
ExecStart=/home/youruser/myapp/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:app
[Install]
WantedBy=multi-user.target
Start and enable:
sudo systemctl start flask
sudo systemctl enable flask
This keeps your app running after reboots.
Deploying on Heroku
web: gunicorn app:app
Deploy:
heroku login
heroku create
git push heroku main
Heroku automatically handles scaling, security, and networking.
Deploying on PythonAnywhere
Upload your project or clone via GitHub.
Configure:
WSGI file path
virtual environment
static files
Simple option for beginners; requires minimal configuration.
Deploying on AWS EC2
Steps:
Launch an EC2 instance
SSH into the server
Install Python, Flask, Gunicorn
Install and configure NGINX
Set up domain & SSL (Certbot)
Example run command:
gunicorn -w 4 -b 127.0.0.1:8000 app:app
EC2 gives full control but requires more setup.
Serving Static Files in Production
In production, static files should not be served by Flask.
NGINX example:
location /static/ {
alias /home/youruser/myapp/static/;
}
This is much faster and more secure than Flask-based static serving.
Using a Virtual Environment
python3 -m venv venv
source venv/bin/activate
Install dependencies:
pip install -r requirements.txt
Virtual environments ensure your deployments are stable and reproducible.
Enabling HTTPS with Certbot (Let's Encrypt)
sudo apt install certbot python3-certbot-nginx
Enable HTTPS:
sudo certbot --nginx -d example.com -d www.example.com
Automatic renewal runs by default.
Environment Variables (Production Settings)
Store secrets in environment variables:
export SECRET_KEY="super-secret"
export DATABASE_URL="sqlite:///prod.db"
Never hardcode credentials in your code.
Best Practices for Flask Deployment
Use Gunicorn or uWSGI as your WSGI server.
Put NGINX or Apache in front of your app.
Serve static files through NGINX.
Run your app as a systemd service for reliability.
Use HTTPS with Let's Encrypt.
Keep secrets in environment variables.
Use a virtual environment and requirements.txt.
Monitor logs with journalctl or NGINX logs.