Updated: 30 May 2024, 09:45+02:00

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 location flask.

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 and 500 + 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 (or application, 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 an flask --app app run, the shorter flask 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 notify the Controller, 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 you are free to choose 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 2 redirect(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, function todo(1) gets executed.

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, named year and month, 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 function todos_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:

flask create todo form

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:

  1. show an input form at a specific URL and
  2. 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 extra methods argument with the list ['GET', 'POST'] as value, we specify that we intend to handle both HTTP GET (the default in Flask) and POST request methods with the subsequent function todos().
  • 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 via GET 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 the todos() function with a GET 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 call request.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 Show 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 via PUT 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 and POST HTTP request methods, but neither PATCH nor DELETE.

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 and POST, but neglect to utilize PATCH, PUT and DELETE: It is quite commonplace that update and delete actions are requested via POST 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 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 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 or 500.html, respectively) the web server would normally send a 200 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 second return value (404 or 500, 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.

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 © 2024 Prof. Dr. Alexander Eck. All rights reserved.