Routing

Part 1, Chapter 4


In this chapter, we'll look at routing.

Prerequisites

This chapter discusses a number of web fundamentals. Before jumping in, you should have a solid understanding of the following Internet and web fundamentals:

  • URL structures
  • Web browsers and web servers
  • HTTP requests and responses

Quickly review the following videos for more info:

What is Routing?

In web applications, the term 'routing' refers to the synchronization of what's displayed to the user with the address (URL) in the web browser.

For example, assuming that you are signed in, if you go to https://testdriven.io/users/account/, you will expect to see your user profile.

This might seem like a simple concept, but it's very important that when a user clicks on a link in your web application, the correct content is displayed (and the address in the web browser reflects that content).

Routing is arguably the most import feature of a web framework. Let's look at how Flask handles it.

Flask Routing

In Flask, you use the route() decorator to bind a function to a URL:

@app.route('/')

In the previous chapter, we created the index() function in app.py and bound that function to the '/' URL:

@app.route('/')
def index():
    return 'Hello World!'

The index() function is responsible for generating the response (Hello World!) that you (the user) sees when accessing the '/' URL:

Index View

Requests and Responses

The following diagram illustrates how Flask handles the request from the web browser for the '/' URL and provides a response back to the browser:

Flask Request Response Diagram

The index() function is often called a 'view' or 'view function', as it is responsible for returning the response to the web browser. This response is usually formatted as text, HTML, or JSON.

So, in the above example, Flask processes the incoming request URL ('/') and then maps the URL to a specific view function, index(), that should handle it. The view function then returns a response, Hello World!, that Flask turns into the response back to the web browser.

Unique Routing

Let's create an 'About' page by defining the about() function in app.py:

@app.route('/about')
def about():
    return '<h2>About this application...</h2>'

The about() function returns an HTML response when the user navigates to the '/about' page:

About View

Methods

The route() decorator accepts an optional second argument: a list of allowable HTTP methods (GET, POST, PUT, DELETE) that the view function can accept.

You can see the associated HTTP methods from the log messages in your terminal window.

For example:

127.0.0.1 - - [30/Dec/2021 08:37:06] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [30/Dec/2021 08:38:07] "GET /about HTTP/1.1" 200 -

Each URL being accessed uses the HTTP GET method, which is the default behavior for the route() decorator.

Flask view functions accept GET requests by default. Thus, @app.route('/about') is equivalent to @app.route('/about', methods=['GET']).

Feel free to add the argument, methods=['GET'] to each of your route() decorators in your code if you want to be more explicit.

To handle different HTTP methods, you can explicitly define them in the methods argument:

@app.route('/example', methods=['GET', 'POST'])
def example():
    return 'an example'

If a view function is going to handle only a single HTTP method, the following shortcut decorators are available:

Full Route Shortcut
@app.route('/example', methods=['GET'] @app.get('/example')
@app.route('/example', methods=['POST'] @app.post('/example')
@app.route('/example', methods=['PUT'] @app.put('/example')
@app.route('/example', methods=['PATCH'] @app.patch('/example')
@app.route('/example', methods=['DELETE'] @app.delete('/example')

As an example, the about view function-

@app.route('/about')
def about():
    return '<h2>About this application...</h2>'

-could use the get() shortcut:

@app.get('/about')
def about():
    return '<h2>About this application...</h2>'

We'll look at different HTTP methods more when we dive into forms and databases.

URL Naming

When defining a URL there are two conventions with regards to a trailing slash:

  1. With: /foo/
  2. And without: /foo

To see how this is handled in Flask, add a /stocks/ route:

@app.route('/stocks/')
def stocks():
    return '<h2>Stock List...</h2>'

You should now have three routes:

  1. /: without a trailing slash
  2. /about: without a trailing slash
  3. /stocks/: with a trailing slash

Test each route in the browser:

  1. http://127.0.0.1:5000/
  2. http://127.0.0.1:5000/about
  3. http://127.0.0.1:5000/stocks/

It's worth noting that Flask will redirect 'http://127.0.0.1:5000/stocks' (without a slash) to 'http://127.0.0.1:5000/stocks/' (with a slash). However, if you attempt to access 'http://127.0.0.1:5000/about/', a 404 (Not Found) error will be returned as there's technically no function mapped to this URL.

Should you use a trailing slash or not?

By convention, a trailing slash at the end of a URL indicates that the URL is a folder or a directory. In other words, when the list of stocks in a user's portfolio is displayed, the /stocks/ route will be used. This approach indicates that you have routes for individual stocks, like /stocks/1, /stocks/2, /stocks/57, and so forth.

Think of it like a filesystem:

  1. Use a trailing slash when accessing a folder in the file system: cd /data/users/
  2. Leave off the trailing slash when accessing a file: cat /data/users/id.txt

Variable Routing

The three routes that we've implemented thus far are considered unique routes, as they do not have any parameters.

There are a number of instances where having to define separate functions for each unique URL will become unreasonable, such as accessing a user profile page or creating separate pages for each stock in a portfolio.

Fortunately, Flask allows us to add variables to a URL, which get passed as arguments to the function bound to that URL. Routes are then dynamically created.

In app.py, add another function called hello_message() to illustrate this concept:

from flask import Flask
from markupsafe import escape

@app.route('/hello/<message>')
def hello_message(message):
    return f'<h1>Welcome {escape(message)}!</h1>'

Here, the following message is displayed when you navigate to 'http://127.0.0.1:5000/hello/patrick':

Hello Message View

Since the URL used is '/hello/patrick', the 'patrick' string is then passed to the hello_message() function.

The use of the escape() function is to prevent a Cross-Site Scripting (XSS) attack, which will be discussed in more detail in a later chapter.

Variable Route Types

Let's add another route to app.py that includes a variable:

@app.route('/blog_posts/<post_id>')
def display_blog_post(post_id):
    return f'<h1>Blog Post #{post_id}...</h1>'

Navigate to 'http://127.0.0.1:5000/blog_posts/23':

Blog View

To add type-checking, add a colon following by a data type constraint:

@app.route('/blog_posts/<int:post_id>')
def display_blog_post(post_id):
    return f'<h1>Blog Post #{post_id}...</h1>'

Test this out again in your browser. What happens if you send a string through -- i.e., 'http://127.0.0.1:5000/blog_posts/one'?

Accepted variable types:

Type Description Examples
string (Default) Accepts any text without slashes About page
int Accepts positive integers Blog Posts page
path Similar to string, but also accepts slashes Search page
uuid Accepts UUID strings Database Access page

Conclusion

The act of mapping to a view function, which then performs some sort of logic before returning something to the user is a core feature for Flask (and any web application, for that matter).

You should now be able to:

  1. Define a route and view function
  2. Explain what routing is and why it's important

We also briefly looked at how to explicitly define the HTTP methods that a view function can handle as well as the types of responses you can send back. We'll explore each of these in much greater detail in future chapters.




Mark as Completed