Skip to content

flask_quick_guide

A Web Application Framework, or simply Web Framework, provides a collection of libraries and tools that help developers build web applications more efficiently. It handles common low-level details like routing HTTP requests, managing sessions, and interacting with databases, allowing developers to focus on the application’s specific logic.

Flask is a lightweight web application framework written in Python. Created by Armin Ronacher as part of the Pallets Projects group (formerly Pocco), Flask is known for its simplicity and flexibility. It’s considered a “microframework” because it provides the core components needed for web development (like request handling, routing, and templating) but leaves other aspects, such as database interaction or form validation, to external libraries called extensions. Flask is built upon two core dependencies: the Werkzeug WSGI toolkit and the Jinja2 template engine, both also from Pallets Projects.

WSGI is the standard interface between Python web applications and web servers. It defines how a web server communicates with a Python application, ensuring that applications written with different frameworks can run on different WSGI-compliant servers (like Gunicorn or uWSGI).

Werkzeug is a comprehensive WSGI utility library. It provides essential components like request and response objects, URL routing, an interactive debugger for development, and various helper functions. Flask uses Werkzeug as its foundation for handling HTTP requests and responses.

Jinja2 is a modern and powerful templating engine for Python. Web templates allow developers to create dynamic HTML pages by embedding variables and control structures (like loops and conditionals) within static HTML markup. Jinja2 renders these templates by replacing placeholders with actual data provided by the Flask application.

As a microframework, Flask aims to keep its core small and easy to extend. It doesn’t include components like a database abstraction layer or built-in form validation. Instead, Flask’s ecosystem relies on extensions to provide these functionalities. This approach gives developers the freedom to choose the tools that best fit their project’s needs. Popular Flask extensions cover areas like ORMs (Object-Relational Mappers), form handling, authentication, and more.

Flask requires Python 3.7 or newer. While older versions of Flask supported Python 2, Python 2 reached its end-of-life in 2020 and is no longer supported. Using the latest stable version of Python 3 is highly recommended for security, performance, and access to modern language features.

It is strongly recommended to use a virtual environment for any Python project. A virtual environment isolates your project’s dependencies from the system-wide Python installation and other projects, preventing version conflicts. Python 3 includes the built-in venv module for creating virtual environments.

Follow these steps to create and activate a virtual environment:

# 1. Create a project directory
mkdir my_flask_app
cd my_flask_app
# 2. Create a virtual environment named 'venv'
# On Windows:
# python -m venv venv
# On macOS/Linux:
python3 -m venv venv
# 3. Activate the virtual environment
# On Windows (cmd.exe):
# venv\Scripts\activate.bat
# On Windows (PowerShell):
# venv\Scripts\Activate.ps1
# On macOS/Linux (bash/zsh):
source venv/bin/activate
# Your shell prompt should now indicate the active environment, e.g., (venv) $

Once the virtual environment is activated, you can install packages specific to your project.

With your virtual environment activated, install Flask using pip:

pip install Flask

This command downloads and installs Flask along with its core dependencies (Werkzeug, Jinja2, ItsDangerous, and Click). You are now ready to start building your Flask application within this isolated environment.

Let’s create a minimal Flask application to verify the installation. Create a file named hello.py and add the following code:

from flask import Flask
# Create an instance of the Flask class
# __name__ tells Flask where to look for resources like templates and static files.
app = Flask(__name__)
# Define a route using the route() decorator
# This binds the URL '/' to the hello_world() function.
@app.route('/')
def hello_world():
# The function returns the response text to be displayed in the browser.
return 'Hello, World!'
# Check if the script is executed directly (not imported)
if __name__ == '__main__':
# Run the application using the built-in development server
# debug=True enables the interactive debugger and reloader
app.run(debug=True)

Key Concepts:

  • from flask import Flask: Imports the main Flask class.
  • app = Flask(__name__): Creates an instance of the Flask application. __name__ is a special Python variable that holds the name of the current module. Flask uses this to determine the application’s root path.
  • @app.route('/'): This is a decorator that registers the hello_world function to handle requests for the root URL (/).
  • def hello_world(): ...: This is the view function. It processes the request and returns the response.
  • if __name__ == '__main__':: This standard Python construct ensures that app.run() is called only when the script is executed directly.
  • app.run(debug=True): Starts Flask’s built-in development web server. debug=True enables debug mode (more details below).

Ensure your virtual environment is activated. Run the script from your terminal:

python hello.py

You should see output similar to this:

* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: ...

Open your web browser and navigate to http://127.0.0.1:5000/. You should see the text ‘Hello, World!’.

The app.run() method accepts several optional arguments:

Sr.NoParameterDescription
1hostHostname to listen on. Defaults to ‘127.0.0.1’ (localhost), accessible only from your computer. Set to ‘0.0.0.0’ to make the server accessible from other devices on your network.
2portPort number to listen on. Defaults to 5000.
3debugDefaults to False. If set to True, enables the debugger and automatic code reloading. Crucially, never use debug mode in production environments due to security risks and performance overhead.

Enabling debug mode (app.run(debug=True) or by setting the FLASK_DEBUG=1 environment variable) provides two key features during development:

  • Reloader: The server automatically restarts when it detects changes in your code files, so you don’t have to manually stop and start it after every modification.
  • Debugger: If an error occurs during a request, Flask displays an interactive traceback in the browser, allowing you to inspect variables and execute code within the context of the error.

Warning: Debug mode allows executing arbitrary Python code from the browser. It is a major security vulnerability and must never be enabled on production servers accessible to the public.

Routing is the mechanism that maps URLs (Uniform Resource Locators) entered by a user or requested by a client to specific Python functions (view functions) in your Flask application. This allows users to access different parts of your application via distinct URLs.

The most common way to define routes in Flask is using the @app.route() decorator. You place it directly above the view function you want to associate with a specific URL path.

@app.route('/about')
def about_page():
return 'This is the About page.'

In this example, visiting http://your-app-domain/about in a browser will execute the about_page() function, and its return value (‘This is the About page.’) will be sent back as the response.

Alternatively, you can achieve the same result using the app.add_url_rule() method. This can be useful in some scenarios, like when generating routes programmatically.

def contact_page():
return 'Contact us here.'
# Equivalent to using the @app.route('/contact') decorator
app.add_url_rule('/contact', 'contact_endpoint_name', contact_page)

The arguments are:

  • '/contact': The URL rule string.
  • 'contact_endpoint_name': The endpoint name. This is typically the name of the view function and is used by url_for() (covered later). If omitted, Flask uses the function name.
  • contact_page: The view function to call.

You can make parts of a URL dynamic by adding variable sections to the route definition. This allows a single view function to handle multiple related URLs.

Variable parts are marked using <variable_name>. By default, the captured value is treated as a string and passed as a keyword argument to the associated view function.

Example:

from flask import Flask
app = Flask(__name__)
# The <username> part is a variable
@app.route('/user/<username>')
def show_user_profile(username):
# The value captured from the URL is passed as the 'username' argument
return f'User Profile: {username}'
if __name__ == '__main__':
app.run(debug=True)

If you run this application and navigate to http://127.0.0.1:5000/user/Alice, the browser will display:

User Profile: Alice

Similarly, visiting http://127.0.0.1:5000/user/Bob will display User Profile: Bob.

You can specify a converter for the variable part using <converter:variable_name>. This tells Flask to expect a certain data type and converts the URL segment accordingly before passing it to the view function.

Sr.NoConverterDescription
1stringAccepts any text without a slash (this is the default).
2intAccepts positive integers.
3floatAccepts positive floating-point values.
4pathLike string, but also accepts slashes (useful for capturing file paths).
5uuidAccepts UUID strings.

Example with Converters:

from flask import Flask
app = Flask(__name__)
@app.route('/post/<int:post_id>')
def show_post(post_id):
# post_id will be an integer
return f'Post Number: {post_id}'
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# subpath can contain slashes
return f'Subpath: {subpath}'
if __name__ == '__main__':
app.run(debug=True)

Run this code:

  • Visit http://127.0.0.1:5000/post/123. Output: Post Number: 123
  • Visit http://127.0.0.1:5000/post/abc. Output: 404 Not Found (because ‘abc’ is not an integer).
  • Visit http://127.0.0.1:5000/path/folder/file.txt. Output: Subpath: folder/file.txt

Flask’s routing, based on Werkzeug, handles trailing slashes in URLs in a specific way to ensure uniqueness and consistency, similar to how web servers like Apache behave:

from flask import Flask
app = Flask(__name__)
@app.route('/projects/') # Note the trailing slash
def projects():
return 'The project page.'
@app.route('/about') # Note: no trailing slash
def about():
return 'The about page.'
if __name__ == '__main__':
app.run(debug=True)
  • Rule with a trailing slash (/projects/): This defines a canonical URL ending with a slash. Accessing /projects/ works directly. Accessing /projects (without the slash) will cause Flask to automatically redirect the browser to /projects/.
  • Rule without a trailing slash (/about): This defines a canonical URL without a slash. Accessing /about works directly. Accessing /about/ (with a slash) will result in a 404 Not Found error.

This behavior helps prevent duplicate content issues for search engines and provides a consistent URL structure.

Hardcoding URLs within your application (e.g., in templates or redirect functions) is generally a bad practice. If you restructure your routes later, you’ll have to find and update every hardcoded URL. Flask provides the url_for() function to dynamically build URLs based on the endpoint name of your view functions.

url_for() takes the endpoint name of the view function as its first argument. By default, the endpoint name is the name of the view function itself. It also accepts keyword arguments corresponding to the variable parts defined in the URL rule.

Example:

from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/admin')
def admin_dashboard():
# View function for the admin dashboard
return 'Admin Dashboard'
@app.route('/guest/<guest_name>')
def guest_greeting(guest_name):
# View function for greeting guests
return f'Hello {guest_name}, welcome as Guest!'
@app.route('/user/<name>')
def user_profile(name):
# This function checks the name and redirects
if name == 'admin':
# Redirect to the admin dashboard using url_for()
# 'admin_dashboard' is the name of the function/endpoint
return redirect(url_for('admin_dashboard'))
else:
# Redirect to the guest greeting page, passing the name
# 'guest_greeting' is the function/endpoint name
# 'guest_name' is the variable part in the target route '/guest/<guest_name>'
return redirect(url_for('guest_greeting', guest_name=name))
if __name__ == '__main__':
app.run(debug=True)

How it works:

  • The user_profile(name) function receives a name from the URL (e.g., /user/admin or /user/Alice).
  • If name is ‘admin’, url_for('admin_dashboard') generates the URL /admin. The redirect() function then sends a response to the browser telling it to navigate to /admin.
  • If name is anything else (e.g., ‘Alice’), url_for('guest_greeting', guest_name=name) generates the URL /guest/Alice by matching the endpoint 'guest_greeting' and filling the variable part <guest_name> with the provided value. The browser is then redirected to /guest/Alice.

Testing the Example:

  • Navigate to http://127.0.0.1:5000/user/admin. The browser will be redirected to http://127.0.0.1:5000/admin and display ‘Admin Dashboard’.
  • Navigate to http://127.0.0.1:5000/user/Bob. The browser will be redirected to http://127.0.0.1:5000/guest/Bob and display ‘Hello Bob, welcome as Guest!’.

Using url_for() makes your application more robust. If you later change the URL path for /admin (e.g., to /admin/panel), you only need to update the @app.route decorator; url_for('admin_dashboard') will automatically generate the correct new URL wherever it’s used.

The HyperText Transfer Protocol (HTTP) is the foundation of communication on the World Wide Web. It defines several request methods (often called “verbs”) that indicate the desired action to be performed on a resource identified by a URL.

Common HTTP Methods:

Sr.NoMethodDescription
1GETRequests data from a specified resource. Should only retrieve data and have no other effect. This is the most common method, used when you type a URL in your browser or click a link.
2POSTSubmits data to be processed to a specified resource (e.g., submitting a form, uploading a file). Often results in a change in state or side effects on the server.
3PUTReplaces the current representation of the target resource with the request payload. Often used for updates.
4DELETEDeletes the specified resource.
5HEADSame as GET, but asks for the response headers only, without the response body. Useful for checking resource metadata like last modified time or size.
6OPTIONSDescribes the communication options for the target resource (e.g., which HTTP methods are allowed).
7PATCHApplies partial modifications to a resource.

By default, Flask routes only respond to GET requests. You can allow other methods by providing the methods argument (a list of strings) to the @app.route() decorator or app.add_url_rule().

Let’s create a simple login form that submits data using POST.

  1. Create an HTML file named login.html in a templates folder:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Please Log In</h1>
<form action="{{ url_for('login') }}" method="post">
<p>Username: <input type="text" name="username"></p>
<p><input type="submit" value="Login"></p>
</form>
</body>
</html>

Note: We use url_for('login') to generate the form’s action URL and specify method="post".

  1. Create the Flask application app.py:
from flask import Flask, request, redirect, url_for, render_template
app = Flask(__name__)
# This route handles displaying the login form (GET) and processing the submission (POST)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# User submitted the form, process the data
submitted_username = request.form['username'] # Access form data via request.form
# In a real app, you would validate the username/password here
return redirect(url_for('welcome', user=submitted_username))
else:
# User is requesting the page via GET, show the login form
return render_template('login.html')
@app.route('/welcome/<user>')
def welcome(user):
return f'Welcome, {user}!'
if __name__ == '__main__':
app.run(debug=True)

Explanation:

  • methods=['GET', 'POST'] tells Flask that the /login route should handle both GET and POST requests.
  • request.method: Inside the view function, we check the HTTP method used for the current request.
  • If request.method == 'POST': We access the submitted form data using request.form, which is a dictionary-like object. request.form['username'] retrieves the value entered in the input field named ‘username’. We then redirect to a welcome page.
  • If request.method == 'GET' (or any other method allowed, but here it’s the else case): We render the login.html template to display the form.
  • request.args: If data were sent as part of the URL query string (common with GET requests, e.g., /search?query=flask), you would access it using request.args.get('query').

Run app.py and navigate to http://127.0.0.1:5000/login. Enter a username and click Login. The form data will be sent via POST, processed by the login() function, and you’ll be redirected to the welcome page.

Generating HTML directly within your Python view functions can quickly become complex and difficult to manage, especially for non-trivial pages. Mixing presentation logic (HTML structure) with application logic (Python code) makes the code harder to read and maintain.

Instead of returning HTML strings from view functions like this:

@app.route('/')
def index_bad():
user = "Alice"
# This is hard to read and maintain!
html = f"""<!DOCTYPE html>
<html>
<head><title>Welcome</title></head>
<body>
<h1>Hello, {user}!</h1>
<p>This is generated directly in Python.</p>
</body>
</html>"""
return html

Flask uses the powerful Jinja2 template engine to separate presentation from logic. You create HTML files (templates) with special placeholders and logic constructs, and Flask renders these templates with data provided by your view functions.

Flask provides the render_template() function to render Jinja2 templates. By convention, Flask looks for templates in a folder named templates located in the same directory as your application script.

Project Structure:

my_flask_app/
├── app.py # Your Flask application script
└── templates/ # Folder for HTML templates
└── index.html

Example:

  1. Create templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<!-- {{ username }} is a Jinja2 variable placeholder -->
<h1>Hello, {{ username }}!</h1>
<p>This page was rendered from a template.</p>
</body>
</html>
  1. Update app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index_good():
user = "Bob" # Data to pass to the template
# Render 'index.html' and pass 'user' as the 'username' variable
return render_template('index.html', username=user)
if __name__ == '__main__':
app.run(debug=True)

When you run app.py and visit /, Flask finds templates/index.html, replaces {{ username }} with the value of the user variable (‘Bob’), and returns the resulting HTML to the browser.

Jinja2 uses specific delimiters to embed logic within HTML:

  • {{ ... }}: For expressions that output a value directly into the HTML (e.g., {{ variable_name }}, {{ user.name }}, {{ 2 + 2 }}).
  • {% ... %}: For control flow statements like conditionals (if/elif/else), loops (for), template inheritance (extends, block), etc.
  • {# ... #}: For comments that are ignored by the template engine and do not appear in the final output.

Example with Conditionals:

  1. Create templates/result.html:
<!DOCTYPE html>
<html>
<head><title>Result</title></head>
<body>
{% if score >= 60 %}
<h1>Congratulations, you passed!</h1>
{% else %}
<h1>Sorry, you failed.</h1>
{% endif %}
<p>Your score: {{ score }}</p>
</body>
</html>
  1. Add a route in app.py:
@app.route('/result/<int:marks>')
def show_result(marks):
return render_template('result.html', score=marks)

Visit /result/75 to see the ‘passed’ message, and /result/40 to see the ‘failed’ message.

Example with Loops:

  1. Create templates/items.html:
<!DOCTYPE html>
<html>
<head><title>Items</title></head>
<body>
<h1>Available Items:</h1>
<ul>
{% for item in item_list %}
<li>{{ item }}</li>
{% else %}
<li>No items available.</li>
{% endfor %}
</ul>
</body>
</html>
  1. Add a route in app.py:
@app.route('/items')
def list_items():
items = ['Apple', 'Banana', 'Orange']
# items = [] # Try with an empty list to see the 'else' block
return render_template('items.html', item_list=items)

This demonstrates how to iterate over a list passed from the view function. The {% else %} block inside the for loop is executed if the sequence (item_list) is empty.

Templates provide a clean way to structure your web pages and keep presentation separate from your Python logic.

Web applications nearly always need to serve static files such as CSS stylesheets, JavaScript files, images, fonts, etc. These files don’t change dynamically based on user requests; they are served directly to the browser.

Flask is configured by default to serve static files from a folder named static located in the same directory as your application script (the root path).

Project Structure:

my_flask_app/
├── app.py # Your Flask application script
├── templates/ # Folder for HTML templates
│ └── index.html
└── static/ # Folder for static files
├── css/
│ └── style.css
├── js/
│ └── script.js
└── images/
└── logo.png

To link to static files in your templates, you should use the url_for() function with the special endpoint name 'static'. The filename argument should be the path to the file relative to the static folder.

Example:

  1. Create static/css/style.css:
body {
font-family: sans-serif;
color: #333;
}
h1 {
color: steelblue;
}
  1. Create static/js/alert.js:
function showWelcomeAlert() {
alert('Welcome to the Static Files Example!');
}
  1. Create templates/static_example.html:
<!DOCTYPE html>
<html>
<head>
<title>Static Files</title>
<!-- Link to CSS file using url_for('static', ...) -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h1>Static Files Example</h1>
<p>This page uses CSS and JavaScript served by Flask.</p>
<button onclick="showWelcomeAlert()">Show Alert</button>
<!-- Include JavaScript file -->
<script src="{{ url_for('static', filename='js/alert.js') }}"></script>
</body>
</html>
  1. Add a route in app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/static-test')
def static_test_page():
return render_template('static_example.html')
if __name__ == '__main__':
app.run(debug=True)

Run app.py and visit http://127.0.0.1:5000/static-test. The page will load with the styles defined in style.css, and clicking the button will trigger the JavaScript function from alert.js. Inspect the page source in your browser, and you’ll see that Flask generated URLs like /static/css/style.css and /static/js/alert.js.

Using url_for('static', ...) ensures that your links will continue to work even if you change the application’s root path or configure a different static file path later.

When a client (like a web browser) sends a request to your Flask application, Flask makes all the incoming data available through the global request object. You need to import it from the flask module to use it within your view functions.

from flask import request

The request object provides access to various parts of the incoming HTTP request. Some of the most important attributes are:

  • request.method: The HTTP method used for the request (e.g., ‘GET’, ‘POST’).
  • request.form: A dictionary-like object containing the parsed data from a submitted HTML form (typically used with POST or PUT requests). Keys are the name attributes of the form elements.
  • request.args: A dictionary-like object containing the parsed contents of the URL query string (the part after the ?). Useful for GET requests.
  • request.values: A combined dictionary containing data from both request.form and request.args. Use with caution, as it might obscure where data originated.
  • request.files: A dictionary-like object containing files uploaded as part of the request (usually via a form with enctype="multipart/form-data").
  • request.cookies: A dictionary containing the cookies sent by the client.
  • request.headers: A dictionary-like object containing the request headers.
  • request.get_json(): Parses incoming JSON request data and returns it as a Python dictionary or list. Raises an error if the request body is not valid JSON or the Content-Type header is incorrect.
  • request.data: Contains the raw incoming request body as bytes. Useful if the data isn’t form data or JSON.
  • request.remote_addr: The IP address of the client making the request. (Note: This might be the address of a proxy if your app is behind one).

Example Usage:

from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/process', methods=['GET', 'POST'])
def process_data():
if request.method == 'POST':
name = request.form.get('name', 'Unknown') # Use .get() for safer access
email = request.form.get('email')
# Process the form data...
return f'POST Request Received! Name: {name}, Email: {email}'
else: # GET Request
search_query = request.args.get('q', 'None') # Get query param 'q'
page = request.args.get('page', '1', type=int) # Get 'page', default to 1, type=int
# Process GET parameters...
return f'GET Request Received! Query: {search_query}, Page: {page}'
# Example HTML form (save as templates/input_form.html)
# <!DOCTYPE html>
# <html><body>
# <form method="post" action="{{ url_for('process_data') }}">
# Name: <input type="text" name="name"><br>
# Email: <input type="email" name="email"><br>
# <input type="submit" value="Submit POST">
# </form>
# <hr>
# <a href="{{ url_for('process_data', q='flask tutorial', page=2) }}">Submit GET</a>
# </body></html>
@app.route('/form')
def show_form():
return render_template('input_form.html')
if __name__ == '__main__':
app.run(debug=True)

Run this app, visit /form. Submitting the form sends a POST request. Clicking the link sends a GET request with query parameters. The process_data function uses the request object attributes accordingly.

Note: Accessing dictionary keys directly (e.g., request.form['name']) will raise a KeyError if the key doesn’t exist. Using the .get('key', default_value) method is often safer as it returns None or a specified default value if the key is missing.

A common pattern in web applications is to:

  1. Display an HTML form to the user (usually via a GET request).
  2. Have the user fill out and submit the form (usually via a POST request).
  3. Process the submitted data on the server.
  4. Display a result page, often showing the data that was submitted or a confirmation message.

We can use Flask’s request object to access the submitted form data and render_template() to pass this data to a result template.

Example:

Let’s create an application that collects student information and displays it on a confirmation page.

  1. Create the Input Form (templates/student_form.html):
<!DOCTYPE html>
<html>
<head><title>Student Information</title></head>
<body>
<h2>Enter Student Details</h2>
<form action="{{ url_for('submit_student_data') }}" method="post">
<p>Name: <input type="text" name="student_name" required></p>
<p>Major: <input type="text" name="major" required></p>
<p>Email: <input type="email" name="email"></p>
<p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
  1. Create the Result Template (templates/student_result.html):
<!DOCTYPE html>
<html>
<head><title>Submission Result</title></head>
<body>
<h2>Student Data Submitted Successfully</h2>
<p>Here is the information received:</p>
<table border="1">
<thead>
<tr><th>Field</th><th>Value</th></tr>
</thead>
<tbody>
<!-- Iterate over the submitted_data dictionary -->
{% for field, value in submitted_data.items() %}
<tr>
<td>{{ field }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p><a href="{{ url_for('show_student_form') }}">Enter another student</a></p>
</body>
</html>

Note: We use submitted_data.items() (Python 3) to iterate over the key-value pairs of the dictionary passed to the template.

  1. Create the Flask Application (app.py):
from flask import Flask, render_template, request
app = Flask(__name__)
# Route to display the form
@app.route('/')
def show_student_form():
return render_template('student_form.html')
# Route to handle the form submission
@app.route('/submit', methods=['POST'])
def submit_student_data():
if request.method == 'POST':
# request.form is an ImmutableMultiDict
# Convert it to a regular dictionary for easier handling
# or pass it directly if the template handles it.
result_data = request.form.to_dict()
# Pass the collected data to the result template
return render_template("student_result.html", submitted_data=result_data)
# Optional: Handle cases where someone accesses /submit via GET
return "Please submit the form via POST.", 405 # Method Not Allowed
if __name__ == '__main__':
app.run(debug=True)

How it works:

  • Visiting / (or the root URL) triggers show_student_form(), which displays student_form.html.
  • When the user submits the form, a POST request is sent to /submit (as specified in the form’s action attribute).
  • The submit_student_data() function handles the POST request.
  • request.form contains the submitted data (e.g., {'student_name': 'Alice', 'major': 'Physics', 'email': 'alice@example.com'}). We use .to_dict() to convert it to a standard Python dictionary, although render_template can often handle the ImmutableMultiDict directly.
  • This result_data dictionary is passed to the student_result.html template under the variable name submitted_data.
  • The Jinja2 for loop in student_result.html iterates through the submitted_data dictionary and displays each field and its value in an HTML table.

Run app.py, navigate to http://127.0.0.1:5000/, fill in the form, and submit. You will be taken to the result page showing the data you entered.

HTTP Cookies are small pieces of data stored by the web browser on the client’s computer. Websites use them to remember information about the user across multiple requests, such as login status, preferences, or items in a shopping cart. Cookies are sent back to the server with every subsequent request to the same domain.

  • Setting Cookies: Cookies are set on the response object. You typically create a response using make_response() and then use the response.set_cookie() method.
  • Reading Cookies: Incoming cookies sent by the browser are available in the request.cookies dictionary-like object.
  • Cookie Attributes: Besides the key and value, you can set attributes like max_age (lifetime in seconds), expires (specific expiry date/time), path (URL path restriction), domain (domain restriction), secure (send only over HTTPS), and httponly (prevent JavaScript access).

Example: Setting and Reading a Cookie

Let’s create an application that asks for the user’s name, stores it in a cookie, and then greets them using the stored name on subsequent visits.

  1. Create templates/ask_name.html:
<!DOCTYPE html>
<html>
<head><title>Enter Name</title></head>
<body>
<h1>What's your name?</h1>
<form action="{{ url_for('set_user_cookie') }}" method="post">
<p>Name: <input type="text" name="username" required></p>
<p><input type="submit" value="Save Name"></p>
</form>
</body>
</html>
  1. Create templates/show_greeting.html:
<!DOCTYPE html>
<html>
<head><title>Greeting</title></head>
<body>
<h1>Hello, {{ user_name_from_cookie }}!</h1>
<p>Your name was remembered using a cookie.</p>
<p><a href="{{ url_for('clear_user_cookie') }}">Forget My Name</a></p>
</body>
</html>
  1. Create the Flask application (app.py):
from flask import Flask, render_template, request, make_response, redirect, url_for
import datetime
app = Flask(__name__)
@app.route('/')
def index():
# Check if the username cookie exists
username = request.cookies.get('username')
if username:
# If cookie exists, show greeting
return render_template('show_greeting.html', user_name_from_cookie=username)
else:
# If no cookie, ask for name
return render_template('ask_name.html')
@app.route('/setcookie', methods=['POST'])
def set_user_cookie():
if request.method == 'POST':
user = request.form['username']
# Create a response object
response = make_response(redirect(url_for('index')))
# Set the cookie
# max_age is in seconds (e.g., 1 year)
max_age_seconds = 365 * 24 * 60 * 60
expires_time = datetime.datetime.utcnow() + datetime.timedelta(seconds=max_age_seconds)
response.set_cookie('username', user, max_age=max_age_seconds, expires=expires_time, httponly=True, samesite='Lax')
# httponly=True prevents JavaScript access (good for security)
# samesite='Lax' helps prevent CSRF attacks
return response
# Handle non-POST requests if necessary
return redirect(url_for('index'))
@app.route('/clearcookie')
def clear_user_cookie():
response = make_response(redirect(url_for('index')))
# Delete the cookie by setting an empty value and past expiry
response.set_cookie('username', '', expires=0)
return response
if __name__ == '__main__':
app.run(debug=True)

How it works:

  • Visiting / checks for the ‘username’ cookie using request.cookies.get('username'). If found, show_greeting.html is rendered. If not, ask_name.html is displayed.
  • Submitting the name form sends a POST to /setcookie.
  • The set_user_cookie function gets the name from request.form, creates a redirect response using make_response(redirect(url_for('index'))), and then uses response.set_cookie() to store the name. Important attributes like max_age, httponly, and samesite are set for better control and security.
  • Visiting /clearcookie uses response.set_cookie() with an empty value and an expiry date in the past (expires=0) to effectively delete the cookie.
  • The browser stores the cookie and sends it back with subsequent requests to the same domain, allowing the / route to retrieve and use it.

Security Note: Standard cookies are stored in plain text and can be easily viewed and modified by the user or intercepted if not using HTTPS. Never store sensitive information like passwords directly in cookies. For session management, use Flask’s session mechanism, which uses cryptographically signed cookies.

While cookies store data on the client-side (browser), sessions typically store data on the server-side. A session allows you to maintain information about a user across multiple requests during their visit (or ‘session’) on your website. Common uses include tracking login status, user preferences, or shopping cart contents.

Flask’s built-in session mechanism uses cryptographically signed cookies. This means:

  • The session data is actually stored on the client within a cookie.
  • Flask uses a secret key (configured via app.secret_key) to digitally sign the cookie.
  • When the client sends the cookie back, Flask verifies the signature using the secret key. If the signature is invalid (meaning the cookie data was tampered with), the session is discarded.
  • This approach avoids the need for server-side storage for simple session needs but has limitations (e.g., cookie size limits, data is visible though not tamperable).

Crucially, you MUST set a secret_key for Flask sessions to work. This key should be a long, random, and unpredictable string, kept confidential, and ideally loaded from an environment variable, not hardcoded in your source code.

Flask provides a session object that behaves like a Python dictionary. You need to import it from flask.

from flask import Flask, session, request, render_template, redirect, url_for
import os # For environment variables
app = Flask(__name__)
# Configure a secret key.
# IMPORTANT: Use a strong, random key and load from environment variables in production!
# Example: app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'default-insecure-key-for-dev')
app.secret_key = 'a-very-secret-random-string-that-is-hard-to-guess'
@app.route('/')
def index():
if 'username' in session:
username = session['username']
return f'''
Logged in as: {username}<br>
Visit <a href="{url_for('logout')}">/logout</a> to log out.
'''
return f'''
You are not logged in.<br>
Visit <a href="{url_for('login')}">/login</a> to log in.
'''
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# In a real app, validate username/password here!
submitted_username = request.form.get('username')
if submitted_username:
# Store username in the session
session['username'] = submitted_username
return redirect(url_for('index'))
else:
return 'Please enter a username.', 400
# Show login form on GET request
return '''
<form method="post">
Username: <input type="text" name="username"><br>
<input type="submit" value="Login">
</form>
'''
@app.route('/logout')
def logout():
# Remove 'username' from the session if it exists
session.pop('username', None) # Use pop with default None to avoid KeyError
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)

How it works:

  • app.secret_key = ...: Configures the secret key necessary for signing the session cookie.
  • session['username'] = submitted_username: In the login function (after POST), the username is stored in the session dictionary.
  • if 'username' in session:: In the index function, we check if the ‘username’ key exists in the session to determine if the user is logged in.
  • username = session['username']: If logged in, we retrieve the value.
  • session.pop('username', None): In the logout function, pop() removes the ‘username’ key from the session, effectively logging the user out. Using None as the second argument prevents an error if the key isn’t present.
  • Flask automatically handles serializing the session dictionary, signing it, setting the cookie on the response, and verifying/loading it from the cookie on subsequent requests.

Security Best Practices for secret_key:

  • Do not hardcode it: Keep it out of your version control system (like Git).
  • Use environment variables: Load the key from an environment variable (e.g., os.environ.get('SECRET_KEY')).
  • Generate a strong key: Use a cryptographically secure random generator (e.g., python -c 'import secrets; print(secrets.token_hex(32))').

For applications requiring server-side session storage (e.g., due to large session data or stricter security needs), extensions like Flask-Session provide various backends (Redis, Memcached, database, filesystem).

It’s often necessary to redirect a user from one URL to another. For example, after successfully logging in, you might redirect the user to their dashboard, or after submitting a form, redirect them to a confirmation page. Flask provides the redirect() function for this purpose.

The redirect() Function

from flask import redirect, url_for
# ... inside a view function ...
return redirect(url_for('target_endpoint_name'))

redirect() takes the target URL as its primary argument. It’s highly recommended to generate this URL using url_for() rather than hardcoding it.

By default, redirect() issues an HTTP status code 302 Found (a temporary redirect). You can specify a different status code, like 301 Moved Permanently, using the code argument:

return redirect(url_for('new_location'), code=301)

Common Redirect Status Codes:

  • 301 Moved Permanently: Indicates the resource has permanently moved. Search engines often update their index.
  • 302 Found: Indicates a temporary redirect. The client should continue using the original URL for future requests.
  • 303 See Other: Often used after a POST request to redirect the client to a new resource using GET, preventing accidental re-submission if the user refreshes.
  • 307 Temporary Redirect: Similar to 302, but mandates that the client does not change the request method (e.g., if the original request was POST, the redirected request should also be POST).

Example: Redirecting after Login

from flask import Flask, redirect, url_for, render_template, request
app = Flask(__name__)
app.secret_key = 'very secret' # Needed for potential session/flash usage
@app.route('/')
def index():
# Display the login form
return render_template('login_form.html') # Assume this template exists
@app.route('/login', methods=['POST'])
def process_login():
# Simulate checking credentials
if request.form.get('username') == 'admin' and request.form.get('password') == 'password':
# Successful login, redirect to success page
return redirect(url_for('login_success'))
else:
# Failed login, redirect back to the index (login form)
# Optionally add a flash message here to indicate failure
return redirect(url_for('index'))
@app.route('/success')
def login_success():
return 'Logged in successfully!'
# Assume templates/login_form.html exists:
# <!DOCTYPE html><html><body><form method=post action="{{ url_for('process_login') }}">
# Username: <input type=text name=username><br>
# Password: <input type=password name=password><br>
# <input type=submit value=Login></form></body></html>
if __name__ == '__main__':
app.run(debug=True)

Sometimes, you need to explicitly signal an error condition, like when a requested resource is not found or a user lacks permission. Flask provides the abort() function for this.

The abort() Function

from flask import abort
# ... inside a view function ...
if not user_has_permission:
abort(403) # Forbidden
if resource_not_found:
abort(404) # Not Found

abort() takes an HTTP status code as an argument and immediately stops the execution of the current view function, returning the corresponding error page to the client.

Common Error Status Codes for abort():

  • 400 Bad Request: The server could not understand the request due to invalid syntax.
  • 401 Unauthorized: Authentication is required and has failed or has not yet been provided.
  • 403 Forbidden: The server understood the request, but refuses to authorize it (user lacks permission).
  • 404 Not Found: The server cannot find the requested resource.
  • 405 Method Not Allowed: The method specified in the request (e.g., POST) is not allowed for the resource.
  • 500 Internal Server Error: A generic error message for unexpected server conditions.

Example: Using abort() for Authorization

from flask import Flask, abort, session # Assuming session is used for login status
app = Flask(__name__)
app.secret_key = 'super secret key'
@app.route('/admin/dashboard')
def admin_dashboard():
if 'user_role' not in session or session['user_role'] != 'admin':
# If user is not logged in or not an admin, abort with 403 Forbidden
abort(403)
# If execution reaches here, user is an admin
return 'Welcome to the Admin Dashboard!'
# Add dummy login/logout routes for testing
@app.route('/login/admin')
def login_admin():
session['user_role'] = 'admin'
return 'Logged in as admin. <a href="/admin/dashboard">Go to dashboard</a>'
@app.route('/login/guest')
def login_guest():
session['user_role'] = 'guest'
return 'Logged in as guest. <a href="/admin/dashboard">Try dashboard</a>'
@app.route('/logout')
def logout():
session.pop('user_role', None)
return 'Logged out.'
if __name__ == '__main__':
app.run(debug=True)

Flask allows you to define custom error handlers using the @errorhandler() decorator to display user-friendly error pages instead of the default ones.

from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
# The error object is passed to the handler
return render_template('errors/404.html'), 404 # Return template and status code
@app.errorhandler(500)
def internal_server_error(error):
return render_template('errors/500.html'), 500

You would then create the corresponding HTML templates (templates/errors/404.html, templates/errors/500.html) to provide a better user experience during error conditions.

Message flashing is a common pattern in web applications to provide feedback to the user after they perform an action. For example, after successfully submitting a form, you might display a temporary message like “Record saved successfully!” on the next page the user visits. Flask provides a simple system for this using the flash() function and the session.

  1. Flashing a Message: In a view function (typically one that handles a form submission or action), you call flash('Your message here'). This stores the message in the user’s session, ready to be displayed on the next request.
  2. Retrieving Messages: In the template of the next page the user visits, you call get_flashed_messages() to retrieve all the messages that were flashed in the previous request. This function returns a list of messages.
  3. Displaying Messages: You iterate through the list returned by get_flashed_messages() and display each message. Once retrieved, the messages are removed from the session, so they only appear once.
  4. Requires Secret Key: Since flashing uses the session, you must have app.secret_key configured.
from flask import flash
# ... inside a view function ...
flash('Profile updated successfully!', 'success') # Message with optional category
flash('Invalid input, please check the fields.', 'error')

Arguments:

  • message: The text of the message to be displayed.
  • category (optional): A string to categorize the message (e.g., ‘success’, ‘error’, ‘warning’, ‘info’). This allows you to style messages differently in your template.

You typically call this within your base template or specific page templates where you want messages to appear.

{# Example in a Jinja2 template (e.g., base.html or index.html) #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}

Arguments:

  • with_categories=False (default): Returns a list of message strings.
  • with_categories=True: Returns a list of tuples, where each tuple is (category, message).
  • category_filter=[] (default): If with_categories=True, you can provide a list of categories to retrieve only messages matching those categories (e.g., category_filter=['error']).

Example: Flashing Login Feedback

  1. Create templates/layout.html (a base template):
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Flask App{% endblock %}</title>
<style>
.flashes { list-style-type: none; padding: 0; margin: 1em 0; }
.flashes li { padding: 0.5em; margin-bottom: 0.5em; border: 1px solid; }
.flashes .success { background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; }
.flashes .error { background-color: #f2dede; border-color: #ebccd1; color: #a94442; }
</style>
</head>
<body>
{# Display flashed messages #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{# Content block for child templates #}
{% block content %}{% endblock %}
</body>
</html>
  1. Create templates/index_flash.html:
{% extends "layout.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome!</h1>
<p>Go to <a href="{{ url_for('login_flash') }}">Login Page</a></p>
{% endblock %}
  1. Create templates/login_flash.html:
{% extends "layout.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<h2>Login Form</h2>
<form method="post">
Username: <input type="text" name="username" value="admin"><br>
Password: <input type="password" name="password" value="password"><br>
<input type="submit" value="Login">
</form>
{% endblock %}
  1. Create the Flask Application (app.py):
from flask import Flask, flash, redirect, render_template, request, url_for
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # IMPORTANT: Use a real secret key
@app.route('/flash-home')
def index_flash():
return render_template('index_flash.html')
@app.route('/flash-login', methods=['GET', 'POST'])
def login_flash():
if request.method == 'POST':
# Simulate checking credentials
if request.form['username'] == 'admin' and request.form['password'] == 'password':
flash('You were successfully logged in!', 'success')
return redirect(url_for('index_flash'))
else:
flash('Invalid username or password. Please try again!', 'error')
# No redirect here, stay on login page to show error
# Alternatively, redirect back to login: return redirect(url_for('login_flash'))
return render_template('login_flash.html')
if __name__ == "__main__":
app.run(debug=True, port=5001) # Use a different port if 5000 is busy

Run the app, go to /flash-home, click the link to /flash-login. Submit the form with correct credentials, you’ll be redirected to /flash-home and see the success message. Submit with incorrect credentials, the error message will appear above the login form.

Handling file uploads is a common requirement for web applications, allowing users to upload images, documents, or other files. Flask makes this process straightforward using the request.files object.

To enable file uploads, your HTML form must meet two criteria:

  1. The method attribute must be set to POST.
  2. The enctype attribute must be set to multipart/form-data. This encoding allows file data to be sent along with the form fields.

Example Form (templates/upload_form.html):

<!DOCTYPE html>
<html>
<head><title>File Upload</title></head>
<body>
<h1>Upload a File</h1>
<form method="post" action="{{ url_for('handle_upload') }}" enctype="multipart/form-data">
<p><input type="file" name="file_to_upload" required></p>
<p><input type="submit" value="Upload"></p>
</form>
</body>
</html>

The <input type="file"> element creates the file selection button.

When a form with enctype="multipart/form-data" is submitted, uploaded files are accessible via the request.files dictionary-like object. The keys correspond to the name attribute of the <input type="file"> elements.

Each value in request.files is a FileStorage object (provided by Werkzeug). These objects behave like file objects but also have additional attributes and methods, most importantly:

  • filename: The original filename provided by the client’s browser.
  • save(destination): Saves the uploaded file to the specified destination path on the server.

Never trust the filename provided by the client directly. It could contain malicious characters or path elements (like ../../../../../etc/passwd) attempting to traverse directories. Always sanitize the filename using Werkzeug’s secure_filename() function before using it in a file path.

from werkzeug.utils import secure_filename
# ... inside view function ...
file = request.files['file_to_upload']
if file and file.filename:
# Sanitize the filename
safe_filename = secure_filename(file.filename)
# Now use safe_filename to build the destination path

secure_filename() removes potentially harmful characters and path separators, returning a safe version suitable for saving.

Example Application:

  1. Create a folder named uploads in your project directory to store uploaded files.

  2. Create the Flask Application (app.py):

import os
from flask import Flask, request, redirect, url_for, render_template, flash
from werkzeug.utils import secure_filename
# Define the path for uploaded files
UPLOAD_FOLDER = 'uploads'
# Define allowed file extensions (optional but recommended)
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Optional: Limit upload size (e.g., 16MB)
app.secret_key = 'secr3t-upl0ad-k3y'
# Ensure the upload folder exists
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
# Helper function to check allowed extensions
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload')
def show_upload_form():
return render_template('upload_form.html')
@app.route('/handle-upload', methods=['POST'])
def handle_upload():
if request.method == 'POST':
# Check if the post request has the file part
if 'file_to_upload' not in request.files:
flash('No file part in the request', 'error')
return redirect(request.url)
file = request.files['file_to_upload']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
flash('No selected file', 'warning')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
try:
file.save(save_path)
flash(f'File "{filename}" uploaded successfully!', 'success')
# You could return a success page or redirect
# return render_template('upload_success.html', filename=filename)
return redirect(url_for('show_upload_form'))
except Exception as e:
flash(f'An error occurred while saving the file: {e}', 'error')
return redirect(request.url)
else:
flash('File type not allowed', 'error')
return redirect(request.url)
# If method is not POST (though route only allows POST here)
return redirect(url_for('show_upload_form'))
if __name__ == '__main__':
app.run(debug=True, port=5002)

Run the app, navigate to /upload, choose a file (e.g., an image or PDF), and click Upload. If successful, the file will be saved in the uploads folder, and a success message will be flashed.

Configuration Options:

Configuration KeyDescription
UPLOAD_FOLDERDefines the directory where uploaded files should be stored. Ensure this directory exists and your application has write permissions.
MAX_CONTENT_LENGTHSpecifies the maximum size (in bytes) allowed for the entire request payload, effectively limiting upload size. Useful for preventing denial-of-service attacks.

Flask is called a “microframework” because its core is intentionally kept small and focused on the essentials: handling requests (WSGI via Werkzeug) and rendering responses (templating via Jinja2). It deliberately omits features like database integration, form handling, user authentication, etc.

This minimalist approach provides flexibility, allowing developers to choose the best tools for their specific needs. Functionality beyond the core is added through Flask extensions. These are Python packages that integrate seamlessly with Flask applications to provide specific features.

A wide variety of extensions are available. You can often find them by searching PyPI (the Python Package Index) for Flask- followed by the feature you need (e.g., Flask-SQLAlchemy, Flask-WTF). The official Pallets Projects website also maintains a list of recommended extensions.

Installation is typically done using pip within your project’s virtual environment:

pip install Flask-SomeExtensionName

Most extensions follow a common pattern:

  1. Import: Import the main class or necessary components from the extension package.
  2. Initialize: Create an instance of the extension’s main class, usually passing your Flask app object to it either directly or later using an init_app() method. This registers the extension with your application.
  3. Configure: Set any required configuration variables in app.config (e.g., database URIs, mail server settings).
  4. Utilize: Use the features provided by the extension (e.g., defining database models, creating forms, sending emails).

Here are some of the most popular and essential extensions (covered in more detail in subsequent sections):

  • Flask-SQLAlchemy: Integrates the powerful SQLAlchemy ORM (Object-Relational Mapper) for database interactions.
  • Flask-Migrate: Handles database schema migrations using Alembic, often used alongside Flask-SQLAlchemy.
  • Flask-WTF: Provides integration with WTForms for creating, validating, and rendering web forms securely (including CSRF protection).
  • Flask-Login: Manages user sessions, handling logging in, logging out, and remembering users.
  • Flask-Mail: Facilitates sending email from your application.
  • Flask-RESTful / Flask-RESTX / Flask-API: Frameworks for building RESTful APIs.
  • Flask-Cors: Handles Cross-Origin Resource Sharing (CORS) headers, necessary for APIs accessed from different domains.
  • Flask-Session: Provides server-side session management with various backends (Redis, Memcached, etc.).
  • Flask-Caching: Adds caching support to your application.

The following sections will delve into the usage of some key extensions.

Sending email notifications, confirmations, or newsletters is a frequent requirement in web applications. The Flask-Mail extension simplifies integrating email sending capabilities into your Flask app by providing an interface to SMTP (Simple Mail Transfer Protocol) servers.

Install the extension using pip:

pip install Flask-Mail

Flask-Mail requires configuration settings in your app.config to connect to your email server. Common settings include:

Sr.NoConfig KeyDescription
1MAIL_SERVERHostname or IP address of the SMTP email server (e.g., ‘smtp.gmail.com’, ‘smtp.mailgun.org’).
2MAIL_PORTPort number of the SMTP server (e.g., 587 for TLS, 465 for SSL, 25 for unencrypted).
3MAIL_USE_TLSBoolean: Enable/disable Transport Layer Security (TLS) encryption (typically used with port 587).
4MAIL_USE_SSLBoolean: Enable/disable Secure Sockets Layer (SSL) encryption (typically used with port 465).
5MAIL_USERNAMEYour username for authenticating with the SMTP server.
6MAIL_PASSWORDYour password or app-specific password for the SMTP server.
7MAIL_DEFAULT_SENDEROptional: Default email address to use as the sender if not specified when creating a message (can be a tuple: ('Sender Name', 'sender@example.com')).
8MAIL_MAX_EMAILSOptional: Maximum number of emails to send in one connection. Defaults to None (unlimited).
9MAIL_SUPPRESS_SENDOptional: Set to True to suppress sending during testing (app.testing=True). Defaults to app.testing.
10MAIL_ASCII_ATTACHMENTSOptional: Boolean. If True, converts attachment filenames to ASCII. Defaults to False.

Security Note: Never hardcode sensitive credentials like MAIL_USERNAME and MAIL_PASSWORD directly in your code. Use environment variables or a secure configuration management system.

Gmail Specifics: If using Gmail, you’ll likely need to enable 2-Step Verification for your Google account and generate an “App Password” to use as MAIL_PASSWORD. Using your regular Gmail password might be blocked for security reasons.

  • Mail: The main class that manages the connection and sending. Initialize it with your Flask app.
  • Message: Represents a single email message, including subject, sender, recipients, body, attachments, etc.

Example Usage:

from flask import Flask
from flask_mail import Mail, Message
import os
app = Flask(__name__)
# --- Configuration (Load from environment variables ideally!) ---
app.config['MAIL_SERVER'] = os.environ.get('MAIL_SERVER', 'smtp.gmail.com')
app.config['MAIL_PORT'] = int(os.environ.get('MAIL_PORT', 587))
app.config['MAIL_USE_TLS'] = os.environ.get('MAIL_USE_TLS', 'true').lower() == 'true'
app.config['MAIL_USE_SSL'] = os.environ.get('MAIL_USE_SSL', 'false').lower() == 'true'
# IMPORTANT: Set these as environment variables
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') # e.g., 'your_email@gmail.com'
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') # e.g., your App Password
app.config['MAIL_DEFAULT_SENDER'] = os.environ.get('MAIL_DEFAULT_SENDER', app.config['MAIL_USERNAME'])
# --------------------------------------------------------------
# Initialize the Mail extension
mail = Mail(app)
@app.route('/send-test-email')
def send_email():
try:
# Ensure username and password are set
if not app.config['MAIL_USERNAME'] or not app.config['MAIL_PASSWORD']:
return "Mail server credentials not configured.", 500
# Create a message
msg = Message(
subject="Test Email from Flask-Mail",
# sender is optional if MAIL_DEFAULT_SENDER is set
# sender=app.config['MAIL_DEFAULT_SENDER'],
recipients=["recipient@example.com"] # Replace with a real recipient
)
msg.body = "This is a plain text email body sent via Flask-Mail."
# Optional: Add HTML body
# msg.html = "<h1>Hello</h1><p>This is an **HTML** email body.</p>"
# Optional: Add attachment
# with app.open_resource("static/images/logo.png") as fp:
# msg.attach("logo.png", "image/png", fp.read())
# Send the message
mail.send(msg)
return "Test email sent successfully! Check the recipient's inbox."
except Exception as e:
app.logger.error(f"Failed to send email: {e}") # Log the error
return f"Failed to send email: {e}", 500
if __name__ == '__main__':
# Before running: export MAIL_USERNAME='your_email@gmail.com'
# export MAIL_PASSWORD='your_app_password'
app.run(debug=True, port=5003)

Steps:

  1. Install Flask-Mail: pip install Flask-Mail
  2. Configure app.config with your SMTP server details (preferably using environment variables).
  3. Initialize Mail(app).
  4. In a view function, create a Message object, setting the subject, recipients, and body/html.
  5. Use mail.send(msg) to send the email.
  6. Run the app and visit /send-test-email.

Remember to replace "recipient@example.com" with an actual email address you can check.

Creating HTML forms manually (<form>, <input>, etc.) and validating the submitted data on the server can be repetitive and error-prone. You need to write HTML for the form fields and then write separate Python code to parse and validate each field’s data from the request object.

The WTForms library provides a powerful way to define forms using Python classes. The Flask-WTF extension seamlessly integrates WTForms into Flask, offering:

  • Form Definition in Python: Define form fields, types, labels, and validation rules within Python classes.
  • HTML Rendering: Easily render form fields (including labels and error messages) in your Jinja2 templates.
  • Data Validation: Apply various built-in or custom validation rules to form fields.
  • CSRF Protection: Automatically generates and validates Cross-Site Request Forgery tokens for enhanced security (requires app.secret_key).
  • File Upload Handling: Integrates with file uploads.

Install Flask-WTF (which includes WTForms as a dependency):

pip install Flask-WTF

You also might need specific validators, like for email:

pip install email-validator

You define a form by creating a class that inherits from flask_wtf.FlaskForm. Inside the class, you define form fields as class attributes, using field types imported from wtforms.

Common WTForms Field Types:

Sr.NoField TypeDescription
1StringFieldRepresents <input type='text'>. The most common text input.
2TextAreaFieldRepresents <textarea>.
3PasswordFieldRepresents <input type='password'> (masks input).
4BooleanFieldRepresents <input type='checkbox'>.
5IntegerFieldA StringField with validation for integers.
6DecimalFieldA StringField with validation for decimal numbers.
7DateField, DateTimeFieldFields for dates and datetimes.
8RadioFieldRepresents a group of <input type='radio'> buttons.
9SelectFieldRepresents a <select> dropdown menu.
10FileFieldRepresents <input type='file'> for file uploads (requires Flask-WTF integration).
11SubmitFieldRepresents <input type='submit'> or <button type='submit'>.

Common WTForms Validators:

Validators are passed as a list to the field definition.

Sr.NoValidatorDescription
1DataRequired (or InputRequired)Checks that the field is not empty.
2EmailValidates that the input looks like an email address (requires email-validator).
3Length(min=-1, max=-1)Validates the length of the input string.
4NumberRange(min=None, max=None)Validates that a number is within a specified range.
5URLValidates that the input is a URL.
6EqualTo('fieldname')Compares the value to another field (e.g., for password confirmation).
7OptionalAllows the field to be empty, skipping subsequent validators if it is.

Example Form Definition (forms.py):

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, SubmitField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class ContactForm(FlaskForm):
name = StringField('Your Name',
validators=[DataRequired(), Length(min=2, max=50)])
email = StringField('Your Email',
validators=[DataRequired(), Email()])
subject = StringField('Subject',
validators=[DataRequired(), Length(max=100)])
message = TextAreaField('Message',
validators=[DataRequired(), Length(min=10)])
subscribe = BooleanField('Subscribe to newsletter?')
submit = SubmitField('Send Message')
  1. Instantiate: Create an instance of your form class in your view function.

  2. Validate: If handling a POST request, call form.validate_on_submit(). This checks if the request is POST and if all validators pass. It also handles CSRF token validation automatically.

  3. Access Data: If validation succeeds, access submitted data via form.<field_name>.data.

  4. Render: Pass the form instance to your template.

Example Application (app.py):

from flask import Flask, render_template, request, flash, redirect, url_for
from forms import ContactForm # Assuming forms.py is in the same directory
import os
app = Flask(__name__)
# IMPORTANT: Set a real secret key, preferably from environment variables
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'you-should-really-change-this')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm() # Instantiate the form
if form.validate_on_submit(): # Handles POST validation & CSRF
# Access validated data
name = form.name.data
email = form.email.data
subject = form.subject.data
message = form.message.data
subscribe = form.subscribe.data
# Process the data (e.g., send email, save to database)
print(f"Received contact form: Name={name}, Email={email}, Subject={subject}, Subscribe={subscribe}")
print(f"Message:\n{message}")
flash('Your message has been sent successfully!', 'success')
return redirect(url_for('contact')) # Redirect after successful POST
# If GET request or validation failed, render the form template
# WTForms automatically adds errors to form.errors if validation fails
return render_template('contact_form.html', title='Contact Us', form=form)
if __name__ == '__main__':
app.run(debug=True, port=5004)

Rendering the Form in a Template (templates/contact_form.html):

Section titled “Rendering the Form in a Template (templates/contact_form.html):”

Flask-WTF makes rendering easy. You can render fields individually or use shortcuts.

<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<style>
.form-field { margin-bottom: 1em; }
.form-field label { display: block; margin-bottom: 0.2em; font-weight: bold; }
.form-field input[type='text'],
.form-field input[type='email'],
.form-field textarea { width: 90%; max-width: 400px; padding: 0.4em; border: 1px solid #ccc; }
.form-field textarea { min-height: 100px; }
.form-field .error { color: red; font-size: 0.9em; margin-top: 0.2em; }
.form-field ul { list-style: none; padding-left: 0; margin-top: 0.2em; }
.flash-success { padding: 1em; background-color: lightgreen; border: 1px solid green; margin-bottom: 1em; }
</style>
</head>
<body>
<h1>{{ title }}</h1>
{# Display flashed messages (optional, but good practice) #}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('contact') }}" novalidate>
{{ form.hidden_tag() }} {# Renders CSRF token and other hidden fields #}
<div class="form-field">
{{ form.name.label }}
{{ form.name(size=30) }} {# Render the input field #}
{% if form.name.errors %}
<ul class="error">
{% for error in form.name.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-field">
{{ form.email.label }}
{{ form.email() }}
{% if form.email.errors %}
<ul class="error">
{% for error in form.email.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-field">
{{ form.subject.label }}
{{ form.subject() }}
{% if form.subject.errors %}
<ul class="error">
{% for error in form.subject.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-field">
{{ form.message.label }}
{{ form.message() }}
{% if form.message.errors %}
<ul class="error">
{% for error in form.message.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="form-field">
{{ form.subscribe() }} {# Render checkbox #}
{{ form.subscribe.label }}
</div>
<div class="form-field">
{{ form.submit() }} {# Render submit button #}
</div>
</form>
</body>
</html>

Key template elements:

  • {{ form.hidden_tag() }}: Essential! Renders the CSRF token hidden field and any other hidden fields defined in the form.
  • {{ form.<field_name>.label }}: Renders the <label> tag for the field.
  • {{ form.<field_name>(**kwargs) }}: Renders the input element itself (e.g., <input>, <textarea>). You can pass HTML attributes as keyword arguments (e.g., form.name(size=30, class_='my-input')).
  • form.<field_name>.errors: A list containing validation errors for that specific field, if any.
  • novalidate attribute on <form>: Prevents default browser validation, allowing WTForms validation to take precedence and display consistent error messages.

Run the app, visit /contact, and try submitting the form with invalid or missing data to see the validation errors.

SQLite is a lightweight, file-based relational database management system (RDBMS). Unlike server-based databases (like PostgreSQL or MySQL), SQLite stores the entire database in a single file on the host machine. It’s included in Python’s standard library via the sqlite3 module, making it incredibly easy to get started with databases in Python projects, including Flask.

Advantages of SQLite:

  • Simplicity: No separate database server process to install or manage.
  • Portability: The entire database is a single file, easy to copy or move.
  • Zero Configuration: Generally requires no setup.
  • Good for Development: Excellent for local development and testing.
  • Suitable for: Low-to-medium traffic websites, embedded applications, prototyping.

Disadvantages of SQLite:

  • Concurrency Limitations: Struggles with high write concurrency (many users trying to write data simultaneously).
  • Limited Scalability: Doesn’t scale across multiple servers like client-server databases.
  • Fewer Features: Lacks some advanced features found in larger RDBMSs.
  • Not Ideal for: High-traffic applications, applications requiring high write performance or complex multi-user scenarios.

While Flask-SQLAlchemy (covered next) provides a higher-level ORM interface, you can interact with SQLite directly using Python’s built-in sqlite3 module.

  1. Setup Database and Table (run once, e.g., in a separate setup_db.py script):
import sqlite3
DATABASE_FILE = 'mydatabase.db'
def setup():
conn = None
try:
conn = sqlite3.connect(DATABASE_FILE)
print(f"Database '{DATABASE_FILE}' opened successfully.")
conn.execute('''
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
address TEXT,
city TEXT,
pincode TEXT
);
''')
print("Table 'students' created or already exists.")
except sqlite3.Error as e:
print(f"Database error: {e}")
finally:
if conn:
conn.close()
print("Database connection closed.")
if __name__ == '__main__':
setup()

Run python setup_db.py to create the mydatabase.db file and the students table.

  1. Flask Application (app.py):
import sqlite3
from flask import Flask, render_template, request, redirect, url_for, flash, g
DATABASE_FILE = 'mydatabase.db'
app = Flask(__name__)
app.secret_key = 'sqlite-example-key'
# Helper function to get database connection
def get_db():
db = getattr(g, '_database', None)
if db is None:
try:
db = g._database = sqlite3.connect(DATABASE_FILE)
# Return rows as dictionary-like objects
db.row_factory = sqlite3.Row
except sqlite3.Error as e:
flash(f"Database connection error: {e}", "error")
return None
return db
# Close database connection automatically after each request
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
# Route to display the form for adding a new student
@app.route('/add-student')
def add_student_form():
return render_template('student_form_sqlite.html') # Assume this template exists
# Route to handle adding the student record
@app.route('/add-record', methods=['POST'])
def add_student_record():
if request.method == 'POST':
db = get_db()
if not db:
return redirect(url_for('student_list')) # Redirect if DB connection failed
try:
name = request.form['nm']
addr = request.form['add']
city = request.form['city']
pin = request.form['pin']
cursor = db.cursor()
# Use placeholders (?) to prevent SQL injection
cursor.execute("INSERT INTO students (name, address, city, pincode) VALUES (?, ?, ?, ?)",
(name, addr, city, pin))
db.commit()
flash("Record successfully added", "success")
except sqlite3.Error as e:
db.rollback() # Roll back changes on error
flash(f"Error in insert operation: {e}", "error")
except KeyError as e:
flash(f"Missing form field: {e}", "error")
finally:
# The connection is closed by @app.teardown_appcontext
return redirect(url_for('student_list'))
# Redirect if not POST
return redirect(url_for('add_student_form'))
# Route to display the list of all students
@app.route('/list')
def student_list():
db = get_db()
if not db:
return render_template('student_list_sqlite.html', rows=[]) # Pass empty list on error
rows = [] # Default to empty list
try:
cursor = db.cursor()
cursor.execute("SELECT id, name, address, city, pincode FROM students")
rows = cursor.fetchall() # Fetch all results
except sqlite3.Error as e:
flash(f"Error fetching records: {e}", "error")
finally:
# Connection closed by teardown function
return render_template('student_list_sqlite.html', rows=rows)
# Simple home page route
@app.route('/')
def home():
return render_template('home_sqlite.html') # Assume this exists
# --- Assume Templates Exist ---
# templates/home_sqlite.html: Links to /add-student and /list
# templates/student_form_sqlite.html: Form with fields nm, add, city, pin, POSTing to /add-record
# templates/student_list_sqlite.html: Displays rows in an HTML table
# ------------------------------
if __name__ == '__main__':
app.run(debug=True, port=5005)

Key Concepts in the Flask App:

  • get_db(): A helper function to establish a database connection. It uses Flask’s g object (a per-request global namespace) to store the connection, ensuring only one connection is opened per request.
  • @app.teardown_appcontext: A decorator that registers a function to run after each request, regardless of success or failure. We use it here to reliably close the database connection (close_connection).
  • db.row_factory = sqlite3.Row: Configures the connection to return rows that can be accessed by column name (like dictionaries) instead of just by index.
  • db.cursor(): Creates a cursor object needed to execute SQL commands.
  • cursor.execute(sql, params): Executes an SQL query. Crucially, use placeholders (?) and pass parameters as a tuple to prevent SQL injection vulnerabilities. Never use f-strings or string formatting to insert data directly into SQL queries.
  • db.commit(): Saves changes (like INSERT, UPDATE, DELETE) to the database.
  • db.rollback(): Reverts changes made since the last commit, typically used in error handling.
  • cursor.fetchall(): Retrieves all rows resulting from a SELECT query.

While using sqlite3 directly works, it requires writing raw SQL and managing connections manually. For more complex applications, using an ORM like SQLAlchemy via the Flask-SQLAlchemy extension is often more productive and maintainable.

Writing raw SQL queries (as shown in the SQLite section) can become cumbersome and error-prone, especially in larger applications. An Object-Relational Mapper (ORM) provides a higher-level abstraction, allowing you to interact with your database using Python objects and methods instead of writing SQL.

SQLAlchemy is the most popular and powerful ORM toolkit for Python. Flask-SQLAlchemy is a Flask extension that seamlessly integrates SQLAlchemy into your Flask application, simplifying configuration and session management.

ORM is a technique that maps:

  • Database tables to Python classes (called Models).
  • Table columns to attributes of these classes.
  • Rows in a table to instances (objects) of these classes.

This allows you to perform database operations (Create, Read, Update, Delete - CRUD) by manipulating Python objects.

Example:

# Instead of:
# cursor.execute("INSERT INTO students (name, city) VALUES (?, ?)", ('Alice', 'London'))
# You might write (using an ORM):
new_student = Student(name='Alice', city='London')
db.session.add(new_student)
db.session.commit()

Benefits of using Flask-SQLAlchemy:

  • Productivity: Write less boilerplate code compared to raw SQL.
  • Readability: Python code interacting with objects is often easier to understand.
  • Maintainability: Changes to the database schema can often be managed within the model definitions.
  • Database Agnosticism: SQLAlchemy supports various database backends (PostgreSQL, MySQL, SQLite, etc.), making it easier to switch databases.
  • Session Management: Flask-SQLAlchemy handles database session setup and teardown automatically within the request context.
  • Integration: Works well with other Flask extensions like Flask-Migrate (for database schema migrations).
  1. Install:
pip install Flask-SQLAlchemy

Depending on the database you use (other than SQLite), you might need to install a database driver:

# For PostgreSQL:
pip install psycopg2-binary
# For MySQL:
pip install mysqlclient # or PyMySQL
  1. Configure Database URI: Set the SQLALCHEMY_DATABASE_URI in your Flask app config. This tells SQLAlchemy where your database is located.
app = Flask(__name__)
# Examples:
# SQLite (relative path)
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'
# SQLite (absolute path - note the extra slashes)
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////path/to/your/mydatabase.db'
# PostgreSQL
# app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@host:port/database_name'
# MySQL
# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlclient://username:password@host:port/database_name'
# Use environment variables for production!
uri = os.environ.get('DATABASE_URL', 'sqlite:///default_dev.db')
if uri.startswith("postgres://"): # Heroku adjustment
uri = uri.replace("postgres://", "postgresql://", 1)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
# Optional: Disable modification tracking for better performance if not needed
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  1. Initialize: Create an instance of the SQLAlchemy class, passing your Flask app object.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)

Models are Python classes that inherit from db.Model. Each class attribute defined using db.Column represents a column in the database table.

class Student(db.Model): # Table name defaults to 'student' (lowercase class name)
# __tablename__ = 'students_table' # Optional: specify custom table name
# Define columns
id = db.Column(db.Integer, primary_key=True) # Auto-incrementing primary key
name = db.Column(db.String(100), nullable=False) # Required string, max 100 chars
city = db.Column(db.String(50))
address = db.Column(db.String(200))
pincode = db.Column(db.String(10))
# Optional: Define an __init__ method for easier object creation
# Not strictly required by SQLAlchemy, but convenient
def __init__(self, name, city, address, pincode):
self.name = name
self.city = city
self.address = address
self.pincode = pincode
# Optional: Define a __repr__ method for helpful debugging output
def __repr__(self):
return f'<Student {self.name} (ID: {self.id})>'

Common db.Column types include db.Integer, db.String(length), db.Text, db.Float, db.Boolean, db.Date, db.DateTime.

Common db.Column arguments include primary_key=True, nullable=False (required field), unique=True, default=value, index=True.

Before you can use your models, the corresponding tables need to exist in the database. You can create them using db.create_all().

# Typically run once, or use Flask-Migrate for managing changes
with app.app_context(): # Need application context
db.create_all()
print("Database tables created (if they didn't exist).")

Note: db.create_all() only creates tables that don’t already exist. It does not update existing tables if you change your models. For managing changes to existing schemas, use a migration tool like Flask-Migrate.

SQLAlchemy operations are performed within a session. Flask-SQLAlchemy manages this session for you, making it available as db.session.

  • Create (Insert):
    student1 = Student(name='Charlie', city='Chicago', address='1 Main St', pincode='60606')
    db.session.add(student1)
    db.session.commit() # Save changes to the database
  • Read (Query):
    # Get all students
    all_students = Student.query.all()
    # Get student by primary key (ID)
    student_by_id = Student.query.get(1) # Returns one student or None
    # Filter results (example: find students in Chicago)
    chicago_students = Student.query.filter_by(city='Chicago').all()
    # More complex filter (example: name starts with 'A')
    a_students = Student.query.filter(Student.name.startswith('A')).all()
    # Get the first result of a filter
    first_chicago = Student.query.filter_by(city='Chicago').first()
  • Update:
    student_to_update = Student.query.get(1)
    if student_to_update:
    student_to_update.city = 'New York'
    db.session.commit() # Commit the change
  • Delete:
    student_to_delete = Student.query.get(2)
    if student_to_delete:
    db.session.delete(student_to_delete)
    db.session.commit() # Commit the deletion

db.session.commit() persists the changes made (adds, updates, deletes) within the current session to the database. db.session.rollback() can be used to discard changes.

Let’s adapt the student application using Flask-SQLAlchemy.

  1. Setup: Install Flask-SQLAlchemy. Configure SQLALCHEMY_DATABASE_URI.

  2. Model (models.py - optional separate file):

# Assuming db object is initialized elsewhere and imported
# from app import db
# class Student(db.Model): ... (definition as above) ...
  1. Flask App (app.py):
from flask import Flask, request, flash, url_for, redirect, render_template
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
# --- Configuration ---
uri = os.environ.get('DATABASE_URL', 'sqlite:///students_sqlalchemy.db')
if uri.startswith("postgres://"):
uri = uri.replace("postgres://", "postgresql://", 1)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'sqlalchemy-so-secret')
# --------------------
db = SQLAlchemy(app)
# --- Model Definition ---
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
city = db.Column(db.String(50))
address = db.Column(db.String(200))
pincode = db.Column(db.String(10))
def __init__(self, name, city, address, pincode):
self.name = name
self.city = city
self.address = address
self.pincode = pincode
# ----------------------
@app.route('/')
def show_all_students():
try:
students = Student.query.all()
except Exception as e:
# Handle potential database errors during query
flash(f"Error fetching students: {e}", "error")
students = []
return render_template('show_all_students.html', students=students)
@app.route('/new-student', methods=['GET', 'POST'])
def add_new_student():
if request.method == 'POST':
# Simple validation (could use Flask-WTF for robust validation)
if not request.form['name'] or not request.form['city'] or not request.form['address']:
flash('Please enter all required fields (Name, City, Address)', 'error')
else:
try:
new_stud = Student(request.form['name'], request.form['city'],
request.form['address'], request.form['pincode'])
db.session.add(new_stud)
db.session.commit()
flash('Record was successfully added', 'success')
return redirect(url_for('show_all_students'))
except Exception as e:
db.session.rollback()
flash(f"Error adding record: {e}", "error")
# Render form for GET request or if POST validation/commit failed
return render_template('new_student_form.html')
# --- Templates ---
# templates/show_all_students.html: Displays student data from the 'students' list in a table.
# Iterate: {% for student in students %} ... {{ student.name }} ... {% endfor %}
# templates/new_student_form.html: Form with fields name, city, address, pincode POSTing to /new-student
# -----------------
if __name__ == '__main__':
with app.app_context():
db.create_all() # Create tables if they don't exist
app.run(debug=True, port=5006)

This version replaces raw SQL and manual connection handling with SQLAlchemy’s object-oriented approach, often leading to cleaner and more maintainable code for database interactions.

The web server included with Flask (app.run()) is designed for development purposes only. It’s convenient for testing and debugging but is not built for performance, security, or stability under production loads. To deploy your Flask application for real users, you need to use a production-ready setup.

Why Not Use the Development Server in Production?

Section titled “Why Not Use the Development Server in Production?”
  • Single-threaded by default: It can only handle one request at a time, leading to poor performance under load.
  • Not optimized: Lacks the efficiency and robustness of dedicated WSGI servers.
  • Security risks: Especially if debug=True is accidentally left enabled, it exposes major security vulnerabilities.

A typical production deployment for a Python web application involves several components:

Client (Browser) <--> Web Server (e.g., Nginx, Apache) <--> WSGI Server (e.g., Gunicorn, uWSGI) <--> Your Flask Application
  1. Your Flask Application: Your Python code.
  2. WSGI Server: A dedicated server that understands the WSGI specification. It runs multiple instances (workers) of your Flask application, manages requests, and communicates efficiently with the application. Popular choices:
  3. Gunicorn: Pure Python, widely used, simple to configure.
  4. uWSGI: More complex configuration but potentially higher performance, written in C.
  5. Web Server (Reverse Proxy): Sits in front of the WSGI server. It handles incoming client connections, serves static files directly (much faster than Python), manages HTTPS/SSL termination, performs load balancing (if needed), and forwards dynamic requests to the WSGI server. Popular choices:
  6. Nginx: High-performance, event-driven, very popular.
  7. Apache: Mature, feature-rich, often used with mod_wsgi (less common now) or as a reverse proxy.

You have several options for where and how to deploy:

PaaS providers abstract away much of the server management. You typically provide your code (often via Git) and a configuration file (like Procfile for Heroku or app.yaml for Google App Engine), and the platform handles provisioning servers, deploying your code, scaling, etc.

Examples:

  • Heroku: Very popular, easy to start with, good documentation.
  • PythonAnywhere: Specifically focused on Python hosting, simple interface.
  • Render: Modern PaaS, offers free tiers for web services and databases.
  • Google App Engine: Google Cloud’s PaaS offering.
  • AWS Elastic Beanstalk: AWS’s PaaS offering.
  • Azure App Service: Microsoft Azure’s PaaS.

PaaS is often the quickest way to get a Flask app deployed.

2. Virtual Private Servers (VPS) / Cloud Compute Instances

Section titled “2. Virtual Private Servers (VPS) / Cloud Compute Instances”

This approach gives you full control over a virtual server. You are responsible for installing the operating system, security updates, the web server (Nginx/Apache), the WSGI server (Gunicorn/uWSGI), Python, your application code, and any databases.

Examples:

  • DigitalOcean Droplets
  • Linode
  • AWS EC2
  • Google Compute Engine
  • Azure Virtual Machines

This requires more system administration knowledge but offers maximum flexibility.

Docker allows you to package your Flask application, its dependencies, Python runtime, and WSGI server into a standardized container image. This image can then be run consistently across different environments (development, testing, production).

Benefits:

  • Consistency: Ensures the environment is the same everywhere.
  • Isolation: Containers run isolated from each other.
  • Portability: Easy to move containers between hosts or cloud providers.
  • Scalability: Container orchestration platforms (like Kubernetes, Docker Swarm, or PaaS services with container support like Google Cloud Run, AWS Fargate, Heroku Docker Deploy) make scaling easy.

Deployment often involves building a Docker image and deploying it to a container registry and then to a container hosting platform.

Even before deploying to a server, you can test your app with a production WSGI server like Gunicorn locally.

  1. Install Gunicorn:
pip install gunicorn
  1. Run Gunicorn:
# Syntax: gunicorn [OPTIONS]MODULE_NAME:VARIABLE_NAME
# Assuming your Flask app instance is named 'app' in 'my_app.py'
gunicorn --workers 4 --bind 0.0.0.0:8000 my_app:app
  • --workers 4: Specifies the number of worker processes to handle requests (adjust based on your server’s CPU cores, typically 2-4 per core).
  • --bind 0.0.0.0:8000: Tells Gunicorn to listen on port 8000 on all available network interfaces.
  • my_app:app: Specifies the Python module (my_app.py) and the Flask application instance variable (app) within that module.

This simulates how your app would run under a WSGI server in production (though usually behind Nginx/Apache).

Choosing the right deployment method depends on your technical expertise, budget, scalability requirements, and desired level of control.