Creating Multiple Routes and Dynamic Content in Flask

How to make a Flask web app with multiple pages and dynamic endpoints.
This article is part of a sequence:
Introduction to Simple Web Applications with Flask
A series on learning how to make simple, one-file Flask apps.

Summary

Up to this point, the kind of Flask apps we’ve built literally do nothing more than just respond with HTML-shaped text. Which means we haven’t created any kind of web experience that a simple HTML text file could provide on its own.

In this lesson, we learn how to create routes that can dynamically respond to whatever kind of URL patterns we need.

Objectives

  • How to define multiple routes in a Flask web app.
  • How to define a route that can generate pages dynamically based on string values in the path string.
Table of contents

Getting started

At the end of this lesson, you'll be able to create an app that can respond to any arbitrary URL path:

dynamic demo

But first, let's revisit the app.py for the most basic Flask app. Try to rewrite it from scratch, just from memory:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def homepage():
    return """<h1>Hello world!</h1>"""

if __name__ == '__main__':
    app.run(use_reloader=True)

We'll be complicating app.py in this lesson so if you don't understand all the moving parts in the Flask app yet, you should at least be familiar enough to know what's being repeated and changed and expanded upon.

Creating multiple routes

http://flask.pocoo.org/docs/0.10/quickstart/#routing

To add a new route, simply call the route() function again with the desired path and create a view function for it:

@app.route('/stanford')
def stanford_page():
    return """<h1>Hello stanford!</h1>"""

Just for fun, let's include a static Google Maps image of "stanford". This is the simplest call (by "call", I mean, URL, as that's how this particular API works) to the Google Static Map API that will return a map image with a marker placed at "stanford":

https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=stanford

Click on that URL to see the image that it generates – note that the URL takes you to an image file, not a HTML webpage that displays a map image.

While we're at it, let's add an image from Google's Street View service; here's the call to that API, according to the Street View Image API:

https://maps.googleapis.com/maps/api/streetview?size=700x300&location=stanford

Let's add both images to the HTML returned by view function binded to the /stanford path:

@app.route('/stanford')
def stanford_page():
    return """
      <h1>Hello stanford!</h1>

      <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=stanford" alt="map of stanford">
  
      <img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=stanford" alt="street view of stanford">
    """

The resulting page when visiting http://localhost:5000/stanford looks like:

image stanford-maps.jpg

Or, if you have a wide enough screen:

image stanford-places-horizontal.jpg

With just a few lines of code, we've got a couple of neat visuals of a place named "stanford". Sure, Google is doing 99.999% of the work, but using a company's work for our own purposes and amusement is why we take the (tedious) time to read through their API documentation.

Making even more routes

But what if we wanted to create a paths for other locations, such as newyork and tokyo? Well, there's always copy and paste:

@app.route('/stanford')
def stanford_page():
    return """
      <h1>Hello stanford!</h1>

      <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=stanford" alt="map of stanford">
  
      <img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=stanford" alt="street view of stanford">
    """

@app.route('/newyork')
def newyork_page():
    return """
      <h1>Hello newyork!</h1>

      <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=newyork" alt="map of newyork">
  
      <img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=newyork" alt="street view of newyork">
      """
@app.route('/tokyo')
def tokyo_page():
    return """
      <h1>Hello tokyo!</h1>

      <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=tokyo" alt="map of tokyo">
  
      <img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=tokyo" alt="street view of tokyo">
    """

Here's what http://127.0.0.1/newyork looks like:

image new-york-maps.jpg

Refactoring practice

OK, you can see how trying to make multiple paths and routes gets out of hand, making our slim app.py bloated with repetitive code. Before we move on to the next, most important phase – creating routes with variables in the path names – let's just do a little cleanup of the app.py code. Remember that even though it's a web application, it's still just a Python script.

Note that the HTML for all of the different routes is basically the same thing, with just the change in the name of location:

    <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=stanford" alt="map of stanford">

We want to abstract it out as a reusable template. In the following snippet, the specific place name, stanford, has been replaced with a placeholder template variable, ${place_name}

    <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=${place_name}" alt="map of ${place_name}">

Using string.Template

If you already know about web app development in other languages, then you may just want to jump to Jinja2 templating, which is the official way to do string templating with Flask apps.

However, assuming you're relatively new to Python and/or web apps:

https://docs.python.org/3/library/string.html#template-strings

What is string templating? Think of it as a better way to substitute values into strings.

Let's take a simple example; we want a way to generate the string, "Hello, SOMEBODY, how are you?", except with SOMEBODY being an actual name:

str_a = "Hello, "
str_b = ", how are you?"
for name in ['Jane', 'Bob', 'Dan']:
    print(str_a + name + str_b)
# Hello, Jane, how are you?
# Hello, Bob, how are you?
# Hello, Dan, how are you?

String templating, at the cost of additional verbosity, gives us a cleaner way of doing this:

from string import Template
my_template = Template("Hello, ${person_name}, how are you?")
for name in ['Jane', 'Bob', 'Dan']:
    print(my_template.substitute(person_name=name))

Here's what that would look like in our current app.py – I like using all-caps variable names, e.g. STATIC_MAP_TEMPLATE, to indicate that the thing represented by the variable is a constant. Helps to separate the things that don't change from the things that do change within a program's execution:

from flask import Flask
app = Flask(__name__)

STATIC_MAP_TEMPLATE = Template("""
<img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=${place_name}" alt="map of ${place_name}">
""")

STREET_VIEW_TEMPLATE = Template("""
<img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=${place_name}" alt="street view of ${place_name}">
""")

@app.route('/')
def homepage():
    return """<h1>Hello world!</h1>"""

@app.route('/stanford')
def stanford_page():
    return("""<h1>Hello stanford!</h1>""" +
            STATIC_MAP_TEMPLATE.substitute(place_name="stanford") +
            STREET_VIEW_TEMPLATE.substitute(place_name="stanford"))

@app.route('/newyork')
def newyork_page():
    return("""<h1>Hello newyork!</h1>""" +
            STATIC_MAP_TEMPLATE.substitute(place_name="newyork") +
            STREET_VIEW_TEMPLATE.substitute(place_name="newyork"))

@app.route('/tokyo')
def newyork_page():
    return("""<h1>Hello tokyo!</h1>""" +
            STATIC_MAP_TEMPLATE.substitute(place_name="tokyo") +
            STREET_VIEW_TEMPLATE.substitute(place_name="tokyo"))

Note how much cleaner each view function looks, with most of the HTML code being taken out and moved to the top. Since the HTML is basically the same for every page, no need to repeat the same HTML in each view function.

Almost all of the HTML is abstracted away. See if you can figure out an even more complete abstraction. Here's my solution below:

from flask import Flask
app = Flask(__name__)

HTML_TEMPLATE = Template("""
<h1>Hello ${place_name}!</h1>

<img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=${place_name}" alt="map of ${place_name}">

<img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=${place_name}" alt="street view of ${place_name}">
""")

@app.route('/')
def homepage():
    return """<h1>Hello world!</h1>"""

@app.route('/stanford')
def stanford_page():
    return(HTML_TEMPLATE.substitute(place_name='stanford'))

@app.route('/newyork')
def newyork_page():
    return(HTML_TEMPLATE.substitute(place_name='newyork'))

@app.route('/tokyo')
def tokyo_page():
    return(HTML_TEMPLATE.substitute(place_name='tokyo'))

The main takeaway is that string templates are just a fancier way to deal with strings. The view functions are still returning strings, as we learned from the very first lesson.


Creating routes with variables

Of course, it's just not feasible for you, the developer, to enumerate every possible route that a user will want to try out, whether it be stanford or chicago or timbuktu.

This is where we get into the core of what makes a web application different from a regular web page: we program our app to accept variable paths.

We already have 95% of the process abstracted. Remember that we started out with a view function that looked like this:

@app.route('/stanford')
def stanford_page():
    return """
      <h1>Hello stanford!</h1>

      <img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=stanford" alt="map of stanford">
  
      <img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=stanford" alt="street view of stanford">
    """

And now it looks like this:

@app.route('/stanford')
def stanford_page():
    return(HTML_TEMPLATE.substitute(place_name='stanford'))

Now we have to abstract the parts of the routing function and view function that explicitly refer to stanford…In pseudocode, it might look something like this:

@app.route('/SOME_PLACE')
def some_place_page():
    return(HTML_TEMPLATE.substitute(place_name=SOME_PLACE))

In fact, that pseudocode is very close to what Flask requires; from its Quickstart documentation on variable rules:

To add variable parts to a URL you can mark these special sections as . Such a part is then passed as a keyword argument to your function.

Here's what that abstracted route/view function can look like; notice what stays the same and what changes:

@app.route('/<some_place>')
def some_place_page(some_place):
    return(HTML_TEMPLATE.substitute(place_name=some_place))

Delete the code regarding the /stanford, /newyork, and other routes (although you should leave the Hello World for /, just to have a page for the root homepage route). We don't need it now. This is what the entire app looks like:

from flask import Flask
from string import Template
app = Flask(__name__)

HTML_TEMPLATE = Template("""
<h1>Hello ${place_name}!</h1>

<img src="https://maps.googleapis.com/maps/api/staticmap?size=700x300&markers=${place_name}" alt="map of ${place_name}">

<img src="https://maps.googleapis.com/maps/api/streetview?size=700x300&location=${place_name}" alt="street view of ${place_name}">
""")

@app.route('/')
def homepage():
    return """<h1>Hello world!</h1>"""

@app.route('/<some_place>')
def some_place_page(some_place):
    return(HTML_TEMPLATE.substitute(place_name=some_place))

if __name__ == '__main__':
    app.run(debug=True, use_reloader=True)

Now, put any name you'd like in your web app's path, e.g. http://127.0.0.1/chicago, http://127.0.0.1/hong+kong, http://127.0.0.1/moscow, http://127.0.0.1/zimbabwe

dynamic demo

Mixing constants and variables in path names

Later down the road, we might have more complex web apps that do more than one kind of thing. What if we wanted to have the current functionality of showing a place via Google Maps, but we also wanted to have a /weather route, which would tell us, well, the weather?

Right now, this is how our app reacts when visiting http://localhost:5000/weather: We show the map and (unavailable) street view for a place named "weather":

image hello-weather.png

So let's create subroutes – which are not much different in concept than creating subfolders within a file folder:

@app.route('/places/<some_place>')
def some_place_page(some_place):
    return(HTML_TEMPLATE.substitute(place_name=some_place))

@app.route('/weather/<n>')
def some_weather_page(n):
    return("I don't know what the weather is in {name}".format(name=n))

Note: for the "weather" route, I'm using the string format() method, which works nearly exactly like the string.Template object with its substitute() method, but with fewer steps. It's more suited for one-line variable substitution.

Make sure you understand Python syntax enough to understand how the following code is functionally equivalent to the previous snippet:

@app.route('/places/<p>')
def places_view(p):
    return(HTML_TEMPLATE.substitute(place_name=p))

@app.route('/weather/<blahblahblah>')
def weather_view(blahblahblah):
    s = """I don't know what the weather is in {zzz}"""
    return s.format(zzz=blahblahblah)

Conclusion

Web applications tend to have more than one page, and so we've learned how to create more than one route to our web app. We've also seen how we can use string templating to make it easier to re-use repetitive HTML and other strings.

Finally, we learned how to create routes with variable names that can dynamically respond to user input. Everything we did in Flask before this was barely different than just writing raw HTML in a text editor and saving it as an HTML file. Now, we're doing something that is impossible to do without a programmatic element.

In the next lesson, we'll practice what we've learned by creating a simple video viewer.

This article is part of a sequence:
Introduction to Simple Web Applications with Flask
A series on learning how to make simple, one-file Flask apps.