Updated: 15 Nov 2024, 14:22+01:00
Work in progress
Flask framework: URL path routing deep dive
Table of contents
A code example accompanying this notebook is available on the
fswd-app
repository.First,
git clone
the repository.Next, run
git checkout flask
to obtain the source code at tagged locationflask
.Alternatively, download a .zip file containing the source code from the GitHub web app.
The exemplary code showcases the following concepts:
- Basic routing, incl. dynamic URL path routing
- (Kind of) RESTful API with
GET
,POST
,PATCH
,DELETE
request methods- Custom error pages
404
and500
+ invoking an error status code
Learning objectives
By the end of this notebook, you will be able to leverage the Flask framework to implement your own small-scale web application written in Python. In particular, we will focus on the URL path routing capabilities of Flask. While we will casually refer to HTML templates, explaining the Jinja2 templating engine is out of scope here.
Flask is considerably more capable than what we will cover in this brief tutorial. At the end you will find recommended follow-ups to learn more.
Rumor has it that companies like Red Hat, Airbnb, Reddit, and Netflix have included Flask to their tech stack - so expect the framework to work also in large-scale application scenarios.
Assumptions
Flask lets you achieve the same result in multiple ways. Here, we assume that you work on a small (i.e. not very complex) web application and will therefore favor simple design choices over more complex ones - often, Flask is flexible in this regard.
For example, you may group related URL routes into individual
Blueprint
objects, and then register these objects with the application.If code modularization is not a concern for your particular project, you can simply disregard this design pattern and manage all URL routes in a central application module.
Compared to other popular web application frameworks written in Python like Django or web2py (these are sometimes called “batteries-included frameworks”), Flask comes with a reduced set of functionalities out-of-the-box. To extend missing functionalities, you may either:
- write missing code from scratch, or
- install, configure and use packaged Flask extensions from third parties.
Throughout this notebook, we will limit ourselves to the built-in functionalities of Flask, in order to keep the barrier of entry as low as possible. In this regard, this notebook is different from both the official Flask tutorial and the Flask quick-start guide, respectively:
- The tutorial provides step-by-step instructions on how to create a small web application. By doing so, it covers advanced topics like the
Blueprint
concept which we won’t discuss here. - The quick-start guide is a whirlwind tour through all major Flask features. Therefore, it serves a different purpose: if you evaluate Flask vs. another framework (e.g., the upcoming FastAPI framework), the quick-start would be a good first resource.
We won’t discuss the important topic of preparing your web application for deployment to production, which among other things requires you to move away from the built-in development web server.
1. Prerequisites
I assume that you have digested all previous notebooks. As a starting point for this notebook, I expect you have:
- created a project folder (e.g., located under
📁C:\Users\me\projects\webapp\
), - set up a Python Virtual Environment (e.g., located under
📁C:\Users\me\projects\webapp\venv\
), - activated this Python Virtual Environment and installed Flask with all its dependencies,
- configured your IDE (such as VS Code) appropriately.
Your folder structure might look something like this (Windows example, macOS looks somewhat differently):
┬ webapp/
├─┬ venv/ <-- Python Virtual Environment folder
│ ├── Include/
│ ├─┬ Lib/
│ │ └─┬ site-packages/
│ │ ├── flask/ <-- Flask package sourced via pip
│ │ ├── jinja2/
│ │ └── …
│ ├── Scripts/
│ └── …
├─ app.py <-- Your main application module
└─ …
2. Hello, World!
You have done this before. Still, it is usually a good idea to implement a “Hello, World!” application to validate that you configured everything fine.
In your project folder, create the file 📄app.py
, if not already existing:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
After saving the file with Ctrl+S (or ⌘+S on macOS), spin up the application with the command:
flask run
Even better than saving manually is turning on auto-save.
As you already know,
flask run
will launch the built-in development server, which is not safe to be used in a production environment.
Open a browser at URL 127.0.0.1:5000
to view the Hello, World!
message.
You should be aware of two conventions when creating a Flask instance:
- First, name the variable holding the Flask instance
app
(orapplication
, but that one is too long for most people).- Second, pass (as a minimum) the
__name__
variable as argument.
If you ever wondered:
__name__
is a special variable in Python, nothing Flask-specific. It basically is a string with the name of the current module (or package, see below) - e.g.,app
if your file is called📄app.py
.You can give the
Flask
constructor an arbitrary string like'foobar'
as input argument instead of the__name__
variable, and “Hello, World!” will execute just fine. Still, there is typically no reason to deviate from this convention, so just stick with it.
3. The flask
command
The Flask package you installed into your Python Virtual Environment for the project includes the flask
command that you access via the terminal. During development, you will call flask run
a lot to start the built-in development web server.
Next, we’ll take a look at some common configuration options that you may find useful.
3.1. Standard file name, app as Python package
The standard file name for a Flask app is 📄app.py
(or 📄wsgi.py
, but nobody uses this one), with the file located in your root project folder. For a small application, you will be just fine sticking to this convention.
However, you can name your app file anything you like. In that case, you have to pass flask
the filename with the --app
option. For example, if your file is called 📄fancyapp.py
:
flask --app fancyapp run
Furthermore, if for whatever reason you choose not to call the Flask instance variable app
, but some other name like myapp
(as in myapp = Flask(__name__)
), you will need to pass the variable name as well:
flask --app fancyapp:myapp run
Alternatively, you might want to contain your Flask application in a Python package. In that case, your folder structure will look something like this:
┬ webapp/
├── venv/
└─┬ packagedapp/ <-- folder that Python shall interpret as package
├── __init__.py <-- this file name makes Python treat the folder as package
└── …
Here, we created a sub-folder named packagedapp
and placed the special 📄__init__.py
inside, so that Python treats this folder as a package. In that case, you must provide flask
the package directory name:
flask --app packagedapp run
A package called
app
won’t need anflask --app app run
, the shorterflask run
would suffice.
To try out how packages work:
- Create a folder
📁app/
.- Inside that folder, create a file
📄__init__.py
and paste the “Hello, World!” code from above.- Launch the web app with
flask run
.
3.2. Debug mode
The flask run --debug
command configures the built-in development server with two features enabled:
- The debugger is enabled (using the debugger is out of scope here).
- The reloader is enabled, which means that the web server will reload any file that you changed and saved. Otherwise, for any change to take effect, you would need to shut down the server (with Ctrl+C / ⌘+. in the terminal) and spin it up again.
For development purposes, this configuration is just fine. You can enable any (or none) of these features also manually by passing appropriate flags to run
:
flask run --debugger --reload
So, to just enable the reloader but not the debugger, write:
flask run --reload
4. Basics to understand Flask: MVC and request-response
4.1. The MVC design pattern
Flask is designed with the Model-View-Controller (MVC) design pattern in mind. MVC is widely used for applications that let users interact with data. That is, MVC is applicable to basically any web application with a user interface and a database:
flowchart BT
Model[<b>Model</b><br />data handling]
View[<b>View</b><br />data representation]
Controller[<b>Controller</b><br />manipulate models & views]
Controller -- <em>may manipulate</em> ---> View
View -- <em>sends requests<br />e.g., user input</em> ---> Controller
Model -- <em>updates when data changes</em> ---> View
Controller -- <em>may send update</em> --> Model
- The Controller responds to a request, usually some user input. For instance, the Controller may update the Model (e.g., add a new to-do item), or manipulate the View (e.g., change to-do item order for display).
- The View defines how data is presented to the user (think front-end HTML, CSS, JavaScript), and provides means for user input (typically some forms).
- The Model holds and manages all (structured) data and the relationship between those data. In a simple to-do app, this might be the to-do items with their properties (name, status, etc.), and the way how they are created, read, updated, and deleted.
The MVC pattern is flexible in the allowed interactions between Controller, View and Model.
For example, the Model might interact exclusively with the Controller bi-directionally - this is indeed how our example To-Do App is structured; or the path from Controller to View might not exist in a particular implementation. The illustration above shows just one example.
Roughly speaking, with the route()
function Flask implements the Controller part of the MVC design pattern. The bundled Jinja2 templating engine provides the View part, and Flask gives you freedom how to handle the Model part.
4.2. The request-response pattern
Flask assumes that you want to control the flow of your application via URL paths: as the app designer, you define static paths - '/'
, '/hello'
, … - or dynamic paths with variables between < >
- '/show/<id>'
, '/pages/<page_num>'
…, and attach some logic to those paths.
For example, a to-do app might display all to-dos at path /todos/
and show a specific to-do at path /todos/<id>
. With the @app.route()
decorator, you tell your web app which function shall be executed at which URL path. We call this “binding an URL path to a function”.
The user (via a browser) requests a defined URL. The web app executes the associated function, which returns something. Finally, the user (again via a browser) receives a response. For better understanding of the request-response pattern, consider this (incomplete and not fully correct) sequence diagram:
sequenceDiagram
autonumber
participant Client as Client (Browser)
participant Server as Web Server
participant App as Web App
participant Function as Function todos()<br />(as part of Web App)
Client ->> Server: HTTP request<br />to path<br />"/todos/1"
Server ->> App: Request<br />resource at path<br />"todos/1"
App ->> App: Look up<br />function that<br />handles path<br />"todos/1"
App ->> Function: `todos(1)`
Function ->> App: `return` value
App ->> Server: `return` value<br />packaged as<br />HTTP response
Server ->> Client: HTTP response
- The Client (i.e. the browser) submits a request to the Web Server for a specific resource (i.e., a specific URL path).
- The Web Server relays this request to our Web App.
- Our Web App looks up the table of all its URL path bindings (collectively created by all calls of
@app.route()
) to identify which Function to invoke. - The looked up Function gets called and does whatever it is meant to do.
- This Function will return something, for example an HTML page to be rendered by the browser.
- The App packages this return value as a nice HTTP response.
- The Web Server submits this HTTP response to the Client.
- Finally, the Client interprets the HTTP response, for example it renders the HTML page it just received.
5. Basic routing
At its core, Flask helps you to implement the control flow of a web application. A web app takes some user input from the browser - for example, a URL typed in the address bar, or data typed into a form - and responds to it, e.g., by displaying some web page or updating a database.
5.1. A minimal routing example
Consider this code snippet from the “Hello, World!” example, which shows the most important feature of Flask:
@app.route('/') # Binds URL at relative path '/' to the following function
def index(): # This function defines what happens on path '/'
return 'Hello, World!'
As you already know, @app.route()
is a Python decorator (it extends functionality of a function). Flask uses the decorator concept to bind a specific URL path - here: /
- to the subsequent function definition.
In our example, this means that whenever the app is at path /
, the function index()
is executed. If the user visits another path - say /foo
- the web server will respond with status code 404
, since we did not specify what shall happen at that path.
In the Flask documentation, the decorated function - here
index()
- is called view function.This is a bit misleading, since you can execute any kind of code there, not just one that returns some kind of View.
5.2. Bind multiple URL paths to the same function
You may decorate a decorated function. Therefore, it is possible to bind more than one URL path to the same function:
@app.route('/')
@app.route('/index')
def index():
return 'Hello, World!'
Now, no matter if you visit '/'
or '/index'
, you will always be greeted with “Hello, World!”.
/index
and/index/
are different URL paths, not the same!
In some cases you want to bind multiple paths to the same function, but always have the same URL visible in the browser’s address bar. For that, Flask lets you initiate a path redirect with the redirect()
function:
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
# Variant 1 (not recommended): supply redirect() a hard-coded URL
@app.route('/foo')
def foo():
return redirect('/')
# Variant 2 (recommended): let url_for() find the URL path
@app.route('/bar')
def bar():
return redirect(url_for('index'))
As you can see, you can supply redirect()
either the redirect URL, or the function that you want executed. In the example above, either visiting the URL path /foo
or /bar
will redirect the user to /
.
It is good practice not to hard-code the redirection path (as in variant 1
redirect('/')
).Rather, call
url_for()
and pass it the function name that you want executed (as in variant 2redirect(url_for('index')
).
5.3. Dynamic URL path routing
Very often you want the application to pass some variable value as part of an URL path. For example, /todos/1
should display to-do with ID 1
, whereas /todos/709
should display to-do with ID 709
.
Flask makes it very easy to implement such a routing rule with its <>
(angle brackets) notation:
@app.route('/todos/<id>')
def todo(id):
sql_query = f'SELECT * FROM todos WHERE id={id}'
pass # Code to query database and display results not yet implemented
return ''
- Define the variable name as part of the URL path between
<>
(angle brackets). - The same variable name should be an input argument of the decorated function (this is not a hard rule, but why would you define a variable in the path without ever using it).
- Within the function, do whatever needs to be done with the input argument.
- When the user visits the URL path, the function is executed with the variable value as input.
- For example, when the browser requests the resource at path
/todos/1
, functiontodo(1)
gets executed.
- For example, when the browser requests the resource at path
To reduce potential errors, it usually makes sense to specify the input data type. In the example above, we might want to set the id
parameter as int
like so: @app.route('/todo/<int:id>')
.
The Flask documentation has a complete list of the available data types. These are:
- string (default data type)
- int
- float
- path
- uuid
- any (this last one is very niche)
If the standard data types are not sufficient for your purpose, you may create custom data types via the
url_map()
function.These data types are actually enforced by Flask, which is different to the more informal type hints system offered by Python out of the box.
One more thing: Dynamic URL path routes may contain more than one variable. Let’s say you want a way to retrieve all to-do items due in a particular month:
@app.route('/todos/due/<int:year>/<int:month>')
def todos_due(year, month):
pass # Code to query database and display results not yet implemented
return ''
Flask turns the string
<int:year>/<int:month>
into two variables, namedyear
andmonth
, respectively, and passes them to the appropriate argument position of your function. So, if you define:@app.route('/todos/due/<int:month>/<int:year>') def todos_due(year, month): pass # Code to query database and display results not yet implemented return ''
…and the browser requests path
/todos/due/11/2023
; then the function gets called like this:todos_due(2023, 11)
.
5.4. Default value in dynamic URL path routes
At times, you want to have an URL path that maps to a function call with a default value. An easy way to achieve this is is like so:
@app.route('/todos/due/<int:year>/<int:month>')
@app.route('/todos/due/', defaults={'year': 2023, 'month': 5})
def todos_due(year, month):
pass # Code to query database and display results not yet implemented
return ''
- Define a second decorator without the variable path, but with the additional argument
defaults
, which gets assigned a dictionary holding the default value(s). - In the example above, if the user visits
'/todos/due/'
, the functiontodos_due(2023, 5)
is executed.
Something to be aware of: if the URL path matches the default value, Flask will automatically redirect to the shorter URL path.
In the example above, visiting the URL
'/todos/due/2023/5'
will redirect to'/todos/due/'
.
6. Retrieving user input
In Flask, there are three primary ways to retrieve user input:
- URL query string: submitted to the web server as HTTP
GET
request (more on HTTP requests later) - HTML form: often submitted to the web server as HTTP
POST
request (we’ll focus on this) - JSON data file: won’t be covered here, refer to the Flask documentation to learn more
6.1. URL query string (HTTP GET
request)
URL query string is easiest to implement, since user input is passed to the web server as part of the URL, for example: www.webapp.com/todos/?action=create&description=Buy-some-milk
.
Flask makes data passed via URL query string available through the function request.args.get()
:
from flask import Flask, request
app = Flask(__name__)
@app.route('/todos/')
def todos():
if request.args.get('action') == 'create': # Test for '/todos/?action=create'
description = request.args.get('description')
pass # Code to query database and give user feedback not yet implemented
return ''
We will not get into more detail here, since this is a bit of a “hacky” way to retrieve user input.
6.2. HTML form (HTTP POST
request)
An HTML form consists of input fields (text input, checkbox, etc.) and a submit button:
<form action="/todos/" method="post">
<input type="text" name="description" />
<input type="submit" value="Create" />
</form>
This is a rendering of the form above:
Upon user click on the submit button (named Create
in the example), the browser sends all input data to the web server at a specific URL (here: /todos/
) via a defined HTTP request method (here: POST
).
A typical pattern is to:
- show an input form at a specific URL and
- let the browser send form data to the web server pointing at the same URL.
Our code then retrieves the form data with the Flask function request.form.get()
.
Here is an example:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/todos/', methods=['GET', 'POST']) # Notice the 'methods' argument
def todos():
if request.method == 'GET':
todos = [] # Currently empty, will hold all to-dos
pass # Code to query database for all to-dos not yet implemented
return render_template('todos.html', todos=todos) # We assume template exists
else: # request.method == 'POST'
description = request.form.get('description')
pass # Code to query database and give user feedback not yet implemented
return redirect(url_for('todos')) # Redirect with 'GET' request
- By passing
route()
the extramethods
argument with the list['GET', 'POST']
as value, we specify that we intend to handle both HTTPGET
(the default in Flask) andPOST
request methods with the subsequent functiontodos()
. - The
request.method
attribute tells us which HTTP request method the URL has been called with. - If no data has been sent yet via the form, the URL path
/todos/
has been called viaGET
method. In that case, we want to show the input form next to a list of all existing to-dos. - Otherwise, we know data has been submitted by the user via
POST
method. We know this since we configured the input form this way. In that case, add the new to-do item to the database (not yet implemented in the example above).
Notice how we
redirect(url_for('todos'))
: this executes thetodos()
function with aGET
request method, which will display all to-do items (including the newly created one).
6.3. The Flask request object
Upon importing request
from the flask
package (i.e., from flask import request
), you get access to the Flask request object. We have already used request.args.get()
and request.form.get()
to retrieve user input. The request object is quite useful, since it provides all client request data (sent via HTTP protocol) in an easily digestible format - namely as attributes and methods.
Here are some commonly used attributes of the request
object:
Request attribute | Description |
---|---|
args | All data submitted by the client via URL query string |
form | All data submitted by the client via HTML form |
values | Combines args & form (if you don’t care how data was submitted) |
files | All files uploaded by the client |
headers | All HTTP headers submitted by the client |
method | The HTTP request method specified by the client |
For all attributes where you expect key-value pairs (all except
method
in the table above) Flask uses a dictionary-like data structure. So you get a value by providing its key.Example: Let’s assume the user submitted a file via HTML form named
baz
(i.e., via HTML element<input type="file" name="baz">
). To retrieve this file, simply callrequest.files.get('baz')
.Note: You may access the value also via dictionary notation, i.e.,
request.files['baz']
. But then you have more effort with handling potential errors.
7. HTTP request methods and RESTful APIs
If you were wondering why the same path (/todos/
in our example) gets overloaded with multiple meanings: this is exactly how a RESTful API is designed by convention. If you stick to this convention, you will likely end up with clean URLs.
Now is the time to briefly explain HTTP request methods. With request methods, the HTTP protocol gives us a way to specify what kind of action we intend to perform by calling a certain URL. The most commonly used methods correspond to “CRUD” operations on data:
CRUD action | SQL | HTTP req. method | To-do example | RESTful API |
---|---|---|---|---|
Create | INSERT | POST | Add new to-do item | /todos/ |
Read | SELECT | GET | Retrieve all to-do items | /todos/ |
Update | UPDATE | PATCH | Update to-do with ID 11 (some attr.) | /todos/11 |
Update | UPDATE | PUT | Update to-do with ID 11 (all attr.) | /todos/11 |
Delete | DELETE | DELETE | Delete to-do with ID 11 | /todos/11 |
- Create action should be requested via
POST
method - Read action should be requested via
GET
method - Update action should be requested via
PATCH
method (if just some attributes shall be updated) or viaPUT
method (if all attributes shall be updated). - Delete should be requested via
DELETE
method
In our example, the URL path bindings might look something like this:
from flask import Flask, request
app = Flask(__name__)
@app.route('/todos/', methods=['GET', 'POST'])
def todos():
if request.method == 'GET':
pass # Meaningful code to show all to-dos missing
return ''
else: # request.method == 'POST'
pass # Meaningful code to create to-do missing
return ''
@app.route('/todos/<int:id>', methods=['GET', 'PATCH', 'DELETE'])
def todo(id):
if request.method == 'GET':
pass # Meaningful code to show one to-do missing
return ''
elif request.method == 'PATCH':
pass # Meaningful code to update some to-do properties missing
return ''
else: # request.method == 'DELETE'
pass # Meaningful code to delete one to-do missing
return ''
HTML forms support just
GET
andPOST
HTTP request methods, but neitherPATCH
norDELETE
.Therefore, when you design an HTML form for user input you can’t leverage the full potential of the HTTP protocol. In practice, a user submitting a data update request or a delete request will usually prompt an HTML form submission via
POST
.
7.1. Non-standard API design
As application designer, you are not forced by the HTTP protocol to adhere to the standard. For example, you can use the URL path /todos/
(called with GET
request method) to show all to-dos, and the separate URL path /todos/create
to add a new to-do item (recommendation: called via POST
method).
Here’s an example to illustrate that point:
@app.route('/todos/') # Remember: default request method is GET
def todos():
pass # Meaningful code to show all to-dos missing
return ''
@app.route('/todos/create', methods=['POST'])
def todo_create():
pass # Meaningful code to create to-do missing
return ''
In practice, many developers are aware of the semantic (and at times practical) differences between
GET
andPOST
, but neglect to utilizePATCH
,PUT
andDELETE
: It is quite commonplace that update and delete actions are requested viaPOST
method. (With HTML forms, you don’t have another option.)And even those developers that try to declare HTTP request methods correctly tend to make mistakes (e.g., always using
PUT
for an update action, even if only one attribute of the data object shall be updated.)Still, it is considered good style to adhere to the HTTP protocol and the conventions of RESTful APIs, as summarized in the table above. Among other things, this will help other developers understand how you structured your URL path routes (One could also say: …how to use your APIs.)
7.2. Distributing HTTP request methods to multiple functions
The many conditional statements above might disturb you. Here are two ways to distribute different HTTP request methods to multiple functions.
The first leverages the methods
argument of the route()
function. It is not forbidden to define one route that handles just GET
, and a second one that handles just POST
, to pick an example:
@app.route('/todos/', methods=['GET']) # methods=['GET'] is default, not strictly needed
def todos():
pass # Meaningful code to show all to-dos missing
return ''
@app.route('/todos/', methods=['POST'])
def todo_create():
pass # Meaningful code to create to-do missing
return ''
If this is too much typing for your taste: Flask provides some dedicated decorator methods as shortcuts to the common routing options:
Method | Shortcut for… |
---|---|
delete('/foo') | route('/foo', methods=['DELETE']) |
get('/foo') | route('/foo', methods=['GET']) |
patch('/foo') | route('/foo', methods=['PATCH']) |
post('/foo') | route('/foo', methods=['POST']) |
put('/foo') | route('/foo', methods=['PUT']) |
With these decorator methods, the example looks like this:
@app.get('/todos/')
def todos():
pass # Meaningful code to show all to-dos missing
return ''
@app.post('/todos/')
def todo_create():
pass # Meaningful code to create to-do missing
return ''
8. Custom error pages
For every HTTP request that a browser sends, the web server will eventually return one or more responses (if the web server is online, that is). The HTTP protocol defines numeric response status codes to codify the response type. If everything goes well, the web server is able to handle the incoming request and send a response back (say, return a view on requested data). This will be accompanied by a status code in the 200 number range, e.g., 200
or 201
.
In case something goes awry, you should actively handle client error responses (400 number range) and server error responses (500 number range), which will make life easier for your users. Luckily, Flask gives us the errorhandler()
function to bind error responses to specific functions. This function is called as decorator, very similar to route()
:
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def http_not_found(e):
return render_template('404.html'), 404 # Notice how we return 2 values here
@app.errorhandler(500)
def http_internal_server_error(e):
return render_template('500.html'), 500
- Note that the decorated function must accept one input argument (called
e
by convention). This variable holds the exact error message, which you might want to hand over to the View for display to the user. - After successfully delivering the custom error page (
404.html
or500.html
, respectively) the web server would normally send a200
status code for success. However, this is not what we want: we want the web server to respond with the error status code. Flask lets us manipulate which status code is delivered by specifying a secondreturn
value (404
or500
, respectively).
Now, you won’t wonder any more how to create custom error pages for your own web application like this one from GitHub.
8.1. Programmatically invoking an error status code
There are situations in which you want your web application to actively invoke an error status code to be delivered by the web server. Flask lets you do just that with the abort()
function:
from flask import Flask, abort
app = Flask(__name__)
@app.route('/todos/<int:id>')
def todo(id):
todo_item = get_todo(id) # Assume get_todo() function exists in your code
if todo_item is None:
abort(404)
return '' # Meaningful user feedback not yet implemented
abort()
takes an HTTP error status code as parameter and ends execution of the function from which it is called. In our example, this means that when the if
condition is satisfied and thus abort()
called, the return
statement will not be evaluated, since the web application already jumped out of the function todo()
.
Annex: Follow-up recommendations
Congratulations, you have taken a deep dive into URL path routing, the central concept of Flask!
Obviously, there is so much more to learn about Flask. I recommend you follow-up with these resources (in that order):
- Walk through the Flask tutorial to reinforce what we’ve covered in this notebook, and extend your knowledge with additional concepts like
Blueprint
. - Diligently read the Flask quick-start guide: It covers all major design concepts and features of Flask, which will give you a solid idea of the full capabilities of the Flask framework.
- Go through the catalog of Flask design patterns and follow-up with those that seem relevant for your particular project. For example, if your application is form-heavy, this pattern gives a solid introduction on how to create forms efficiently by using the Flask-WTF extension. Or if you have pages that require user login for access, this pattern will save you time and reduce errors. (Or, leverage Flask-Login for the same effect.)
Other worthwhile resources include:
- The Flask tutorial by Miguel Grinberg. While a few years old and some things in Flask work different now, it is a quite diligent intro that dives into interesting Flask extensions such as Flask-Login for user login handling and Flask-Bootstrap for advanced styling.
- The Flask tutorial on Hackers and Slackers, which is also a few years old. If you went through the official Flask tutorial and the tutorial by Miguel Grinberg many things will become repetitive. Still, it explains a few things better than both other tutorials (and this notebook).
- Flask tutorials on Realpython and on Freecodecamp, respectively (for both: registration required, access to articles may be limited). Some tutorials might be relevant for your particular project (e.g., how to send email confirmations or how to implement user authentication). Having said that, some tutorials are rather old and the latest version of Flask framework or the featured extensions might work differently now.
Finally, if you don’t find satisfying answers on Stackoverflow to your pressing questions, head over to the Flask Subreddit.
Copyright © 2025 Prof. Dr. Alexander Eck. All rights reserved.