• Flask + Angular Full-Stack Tutorial Part 3 - Flask (5-17-16)

    Contents:

    1. Intro
    2. Installing Flask
    3. First Flask App
    4. Running the Server
    5. More Routes
    6. Adding HTML Files
    7. Jinja Templating
    8. Some CSS
    9. Wrapping Up

    Intro

    In Part 2 of this tutorial series, we learned how to use virtual Python environments in order to keep our app’s dependencies isolated from other projects. In this tutorial, we are going to learn about the Flask, which is the Python micro-framework that is going to power the back-end of our app.

    Flask website

    The Flask documentation is fairly straight-forward, so I would recommend skimming through it or at least reading the Quickstart.

    Installing Flask

    Installing Flask is extremely easy. In a bash terminal, make sure that you have the pet-app virtual environment activated and type:

    
      pip install Flask
      

    This will install Flask and its dependencies into your virtual environment. Now we are ready to write a very (extremely) basic Flask application!

    First Flask App

    Change into the pet-app directory, create a new file called “server.py”, and save it in the pet-app directory. This is the file that will initialize our Flask application. Type the following into server.py (making sure to indent things precisely or Python will yell at you) and save the file:

    
      from flask import Flask
      app = Flask(__name__)
    
      @app.route('/')
      def indexRoute():
        return "This is the index"
    
      if __name__ == '__main__':
        app.run()
      

    Let’s go over things line by line. The first line imports the Flask class as a dependency for the code in this file. Eventually, we will be importing more and more classes and packages as dependencies to extend the functionality of our app.

    Line 2 defines our application object. “app” is the variable name we are choosing for our Flask class instance. The first argument to Flask() is the name of the application module. Since we are only using one module right now, we will put “__name__”.

    “@app.route(‘/’)” establishes a URL route on our app and will execute a corresponding function when someone visits the ‘/’ route.

    The two lines below that define the function that should be executed when someone visits the specified route. “def indexRoute():” begins the definition of the function indexRoute(), and “return ‘This is the index’” is the body of the function. Note the tab in front of the body of the function: in Python, scope of code blocks correspond to their indentation levels. When you define a function, all of the code for that function must be at least one indentation level further in than the “def” line in order for that code to be interpreted as the function’s body.

    Below that, the “if __name__ == ‘__main__’” conditional checks to make sure that the server file is executed directly from the Python interpreter (more about what that means later).

    The last line, “app.run()”, calls the app object’s run() function to run Flask’s built-in local server for our application, which will allow us to preview the app in our own development environment without needing to upload it to a production web server.

    Running the server

    In the terminal, change into the pet-app directory and type:

    
      python server.py
      

    You should then see a line similar to this appear below what you just typed:

    
     * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
     

    You can then open that link in your browser to view the app, and since it will open to the default ‘/’ route, you should see ‘This is the index’, which is what we defined our function to display when someone visits this URL.

    For Cloud9, the app requires a small amount of additional setup to work properly. In the “app.run()” function, you’ll need to provide information about the host and port environment variables of the application in order for Cloud9 to launch your app correctly. In server.py, replace the current app.run() line with:

    
      app.run(host='0.0.0.0', port=8080)
      

    This will run your app with a base URL of 0.0.0.0 and a port of 8080, so now if you type “python server.py” into a terminal you will see:

    
      * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
      

    To stop the server, use the keyboard shortcut Ctrl-C. When you make changes to your code, sometimes you will need to restart the server in order for the changes to take effect. But we can tell the Flask development server to automatically refresh itself whenever it detects a change in the code by enabling Debug mode. To enable Debug mode, in the “app.run()”” line of server.py, you can set the debug environment variable to True:

    
      app.run(host='0.0.0.0', port=8080, debug=True)
      

    To demonstrate this working, start the development server (“python server.py”), then type a new sentence in the return statement for the indexRoute() function in the server.py file (replace “This is the index” with something else). When you save the server.py file, the server will automatically refresh when it detects the change. You should see an output in the terminal similar to the following:

    
      * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
      * Restarting with stat
      * Debugger is active!
      * Debugger pin code: 255-867-292
      * Detected change in '/home/ubuntu/workspace/pet-app/server.py', reloading
      * Restarting with stat
      * Debugger is active!
      * Debugger pin code: 255-867-292
      

    If you refresh the page in the browser where your app is running, you should see that the changes you made to the server.py file have been applied.

    More Routes

    Let’s add some more routes that we anticipate the app should have (perhaps routes to access a page of users and a page of pets). In server.py, we will add two more routes the same way as we defined the index route:

    
      @app.route('/users')
      def usersRoute():
      return "This is the users page"
      
      @app.route('/pets')
      def petsRoute():
      return "This is the pets page"
      

    Now if we navigate to the URL our app is running at and add ‘users’ or ‘pets’ to the end of the URL, we will see the results from what we defined in each route’s corresponding function:

    
      http://pet-manager-prowland.c9users.io:8080/users
      

    This URL will result in “This is the users page” being displayed. We can also handle variables from the URL and use them in our route functions. For example, let’s imagine that we want to display information about a particular pet based on a pet id number. To do this, we set the variable we want to pull from the URL in triangle brackets inside the route definition (and since this page is going to be separate from the generic pets page, this chunk of code should be separate from the other “/pets” route):

    
      @app.route('/pets/<pet_id>')
      def petRoute(pet_id):
        return "This is the page of pet {0}".format(pet_id)
      

    There are some new parts in this route definition that deal with how the URL variable (the stuff inside the triangle brackets) is handled. The first line, “@app.route(‘/pets/<pet_id>’)”, means that we are going to execute this route’s function whenever someone acceses the pets URL with a /123 (or other id number) after it. In the line “def petRoute(pet_id):”, the name of the URL variable we type into the triangle brackets from the line above gets listed as an argument to the route’s corresponding function. In the return statement, we are using a format specifier to display the variable from the URL in the string. The “{0}.format(pet_id)” means “display the 0th element in the variables/values listed as the format() parameter(s)”.

    So if we navigate to this URL:

    
      http://pet-manager-prowland.c9users.io:8080/pets/123
      

    We will get “This is the page of pet 123” as the result. This is significant because using URL variables in this manner is a main component of the API we are going to be building for the app. So far, the server.py file should look like this:

    
      from flask import Flask
      app = Flask(__name__)
    
      @app.route('/')
      def indexRoute():
        return "This is the index!"
        
      @app.route('/users')
      def usersRoute():
        return "This is the users page"
        
      @app.route('/pets')
      def petsRoute():
        return "This is the pets page"
        
      @app.route('/pets/<pet_id>')
      def petRoute(pet_id):
        return "This is the page of pet {0}".format(pet_id)
    
      if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8080, debug=True)
      

    Adding html files

    Since we now know how to display a particular line of text depending on which URL is visited, we can extend that feature by displaying an html file instead. Our html file will be located in a separate directory that will organize our “static” files, so let’s first create a new directory called “static” inside our pet-app directory:

    
      mkdir static
      

    Next, let’s tell our flask app where to look for the static files. This is accomplished by setting the “static URL path” inside the app declaration. So in the “app = Flask(__name__)” line, add a second parameter to the Flask() function that defines the location of the static URL path:

    
      app = Flask(__name__, static_url_path='')
      

    We set the static_url_path to an empty string since Flask will automatically look for a directory named “static” where the server file is located. Now create an html file named “index.html” and save it into the static directory. Fill the html file with this code:

    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App index page.</h1>
          </body>
      </html>
      

    This is a very basic html file with only a title in the head tags and a header in the body tags. We want the index.html file to correspond to the index route (‘/’), so in our server.py file we are going to modify the indexRoute() return statement. Instead of returning a string, we are going to return a function that tells the app to load a particular file, which is listed as the function’s argument. Update the current routing code for the index so that it looks like this:

    
      @app.route('/')
      def indexRoute():
          return app.send_static_file('index.html')
      

    “app.send_static_file(‘index.html’)” tells our app to send the file we list as the argument for send_static_file() to the client (the user visiting our app). Notice that even though the path to index.html (from the perspective of server.py) is static/index.html, we are omitting the “static/” part of the path inside app.send_static_file(). Since we are sending a static file, Flask will search for the file listed as the argument for send_static_file() inside the static directory automatically.

    With your server.py file running, visit your app’s URL in the browser and you will see the index.html file rendered. Awww yeah! :sunglasses: In order to render html pages for the users and pets aspects of our app, we could write out each html file completely like so:

    
      Index.html:
    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App index page.</h1>
          </body>
      </html>
    
      Users.html:
    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App users page.</h1>
          </body>
      </html>
    
      Pets.html:
    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App pets page.</h1>
          </body>
      </html>
    
        Pet.html:
    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App specific pet page.</h1>
          </body>
      </html>
      

    But there are a few problems with this method. One problem is the large amount of repeated code appearing in each file. An example of why this is problem is the scenario where we want to update the title. In our current arrangement, we would have to edit the title in each html file (rather than being able to edit it in one place). A second problem is that the file sizes of our individual html files would balloon in size over time as we added more content. This would negatively impact page load speeds, which, in turn, would negatively impact our app’s usability. A third issues is that our html files currently are not modular. There is no separation of concerns when it comes to the html header, body, and individual page content; it’s all lumped together. We can address these issues by using Flask’s built-in templating system, Jinja.

    Jinja templating

    Templating allows us to organize the different parts of our html content into separate files. For example, we could have a layout file that contains the “shell” html components (such as the html, head, and body tags), and a series of page files that contain only the html pertaining to the subject of that page (like our h1 tags in the previous code example). Flask will search for html files to interpret as templates in a directory named “templates”, so in the pet-app directory (ensure you are not in the static directory) create a new directory called templates:

    
      mkdir templates
      

    Now let’s create a file similar to our original index.html, except we will add a couple of lines specific to the Jinja templating system. Create and save this file into the templates directory as “layout.html”:

    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
          </head>
          <body>
              <h1>Pet App</h1>
              {% block content %}{% endblock %}
          </body>
      </html>
      

    The line “{% block content %}{% endblock %}” is where our page-specific html will be inserted. The “{% %}” are Jinja delimiters that tell Jinja to render whatever is inside them as a statement. The statement we are executing is the loading of the block named “content”. When the block content statement is rendered, jinja will replace “{% block content %}{% endblock %}” with the html code associated with the block named “content”, which can be defined in another file.

    Now that we have the “shell” html defined, we can create other html files that can be inserted into the shell html via the loading of the block content by Jinja. In the templates directory, create a file called “index.html” and type the following inside:

    
      {% extends "layout.html" %}
      {% block content %}
        <p>You are now at the index page</p>
      {% endblock %}
      

    The first line tells Jinja that this file is going to be incorporated into the layout.html file. Jinja will look for any blocks referenced inside layout.html in the files that extend layout.html (like our new index.html file). So the html inside the block content we are defining in index.html is what is going to be inserted into the block content statement in layout.html.

    Using Jinja templating requires that we perform some additional setup in our server.py file. First, we will need to import the function that will make rendering the templates possible, which is render_template. At the top of the server.py file, we need to add render_template as a dependency for our app:

    
      from flask import Flask, render_template
      

    We can then use render_template(“index.html”) as the new return value for our indexRoute() function, replacing app.send_static_file():

    
      @app.route('/')
      def indexRoute():
          return render_template("index.html")
      

    Run your server.py file. This time, the text “You are now at the index page” appears beneath the “Pet App” h1 header we wrote in the layout.html file, which means our template rendered correctly! The benefits of using Jinja templating become clearer when we add more partials (files like index.html that only contain a “partial” amount of html code). So let’s add three more partials, one for each of our three current routes: users, pets, and pet. In the templates directory, create three new files: users.html, pets.html, and pet.html.

    In users.html, place the following code inside:

    
      {% extends "layout.html" %}
      {% block content %}
        <p>You are now at the users page</p>
        <p>List of users:</p>
        <ul>
          <li>Billy</li>
          <li>Sally</li>
        </ul>
      {% endblock %}
      

    In pets.html, insert this code:

    
      {% extends "layout.html" %}
      {% block content %}
        <p>You are now at the pets page</p>
        <p>List of pets:</p>
        <ul>
          <li>Doggy</li>
          <li>Kitty</li>
        </ul>
      {% endblock %}
      

    And in pet.html, insert this code:

    
      {% extends "layout.html" %}
      {% block content %}
        <p>You are now at the page of pet
          {% if pet_id %}
            {{ pet_id }}
          {% endif %}
        </p>
      {% endblock %}
      

    The code in users.html and pets.html is very similar to index.html, but pet.html has some additional lines we have not seen yet. Since our petRoute(pet_id) function in server.py handles a variable from the URL, we can pass that variable into the Jinja template and place it where we want to. First, {% if pet_id %} checks that the name of the variable we want to load has been defined from the server.py render_template() function call. In server.py, when we call render_template() for pet.html we will also be passing the URL variable pet_id as a second parameter to the function. So the {% if pet_id %}{% endif %} conditional is checking to make sure that pet_id was defined as a parameter in petRoute()’s’ render_template() function. Inside the conditional, we can use the variable by including it inside double curly-brackets.

    Now let’s update our server.py file to incorporate the new html files we created. Update the server.py routes so that their return statements use render_template:

    
      @app.route('/')
      def indexRoute():
        return render_template("index.html")
        
      @app.route('/users')
      def usersRoute():
        return render_template("users.html")
        
      @app.route('/pets')
      def petsRoute():
        return render_template("pets.html")
        
      @app.route('/pets/<pet_id>')
      def petRoute(pet_id):
        return render_template("pet.html", pet_id=pet_id)
      

    The render_template() function in petRoute() defines pet_id on the left of the equals sign (which is the variable we are going to send to the template) to equal pet_id from the URL variable. We could have called the leftmost pet_id anything, as long as we make sure that its name matches the name we use in the corresponding html file’s Jinja statements.

    Some css

    Just for fun, we will do some basic styling of our app (later we will go into style much more in depth, at this point in the development process we are going to focus mostly on the backend). A very quick way of accomplishing a decent look for a website is by using the bootstrap library. We are going to load the bootstrap code via a CDN (Content Delivery Network). This means that the bootstrap code is hosted on a server somewhere and that we can link to the URLs of the bootstrap files in order to use them. Alternatively, we could download the bootstrap source code, put the files in our static directory, and create link/script tags that load them locally, but CDN works just as well. In the head section of our layout.html file, we are going to add links to the bootstrap core css, the default bootstrap theme css, and the bootstrap javascript file:

    
        <head>
            <title>Pet App</title>
    
            <!-- Latest compiled and minified css -->
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    
            <!-- Optional theme -->
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
    
            <!-- Latest compiled and minified JavaScript -->
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
        </head>
      

    The code that loads the bootstrap resources may look complicated, but each link/script tag can be broken down into three general components: type, source, and security. In the link tags (which we are using to load the css files), rel=”stylesheet” means that the resource we are loading in the href=”” attribute should be interpreted as a stylesheet. In the script tag, the type of resource loaded in the src=”” attribute will automatically be interpreted as javascript unless we specify otherwise. In both the link and script tags, the integrity and crossorigin attributes ensure that the resourse we are attempting to load is genuine and not tampered with along the way from the server to our computer. Integrity and crossorigin are necessary only when loading things from a CDN; we do not need them for loading local files.

    The bootstrap javascript code requires the jquery library in order to work, so we are going to add a script tag that loads jquery BEFORE the link/script tags that load bootstrap:

    
        <head>
            <title>Pet App</title>
    
            <script src="https://code.jquery.com/jquery-1.12.3.min.js" integrity="sha256-aaODHAgvwQW1bFOGXMeX+pC4PZIPsvn2h1sArYOhgXQ=" crossorigin="anonymous"></script>
    
            <!-- Latest compiled and minified css -->
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    
            <!-- Optional theme -->
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
    
            <!-- Latest compiled and minified JavaScript -->
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
        </head>
      

    Since we have the bootstrap and jquery code linked to our app, we’ll see an immediate change when we refresh the page our app is loaded on. This is due to the css rules that the bootstrap stylesheets define for the basic html tags. To make our pages look a little sharper, we are going to use some bootstrap-specific style classes and components. Bootstrap has some excellent starter templates that you can use to setup a basic good-looking website. We are going to use/modify the code from the navbar-static-top example. In layout.html, update the code inside the body tags to incorporate bootstrap:

    
        <body>
    
            <nav class="navbar navbar-default navbar-static-top">
              <div class="container">
                <div class="navbar-header">
                  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                  </button>
                  <a class="navbar-brand" href="/">Pet App</a>
                </div>
                <div id="navbar" class="navbar-collapse collapse">
                  <ul class="nav navbar-nav">
                    <li><a href="/">Home</a></li>
                    <li><a href="users">Users</a></li>
                    <li><a href="pets">Pets</a></li>
                  </ul>
                </div>
              </div>
            </nav>
        
        
            <div class="container">
              <div class="jumbotron">
                <h1>Pet App</h1>
                {% block content %}{% endblock %}
              </div>
            </div>
            
        </body>
      

    We added two main components: a navbar and a jumbotron. The navbar contains links to the index, users, and pets pages of our app. Notice the hrefs of the li (list item) tags inside the nav ul (unordered list). The strings inside the href attributes reflect how we defined our routes in the server.py file. We also moved the {% block content %}{% endblock %} statement to inside the jumbortron, which is a bootstrap component for displaying something prominent. If we click on the different menu options in the nav-bar, the corresponding html for each page should be loaded inside the jumbotron. Besides looking sleek, another cool thing about bootstrap is that it takes into account tablet and mobile screen sizes in addition to desktop dimensions. If you resize the browser your app is loaded in, you’ll see that at smaller widths the menu bar collapses and can be toggled by a menu button (the toggling action is accomplished via the jquery and javascript bootstrap files we loaded in the head tags).

    Let’s take the style just a bit further by utilizing font-awesome icons. We are able to load the font-awesome code via a CDN, and since bootstrap does not require font-awesome as a dependency (and vice-versa), we can include this link tag in the head section below our current link and script tags:

    
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
      

    This will allow us to add icons to our html elements by adding font-awesome specific classes. For example, if I want to add a book icon, I would first look up on the font-awesome website to see what class I need to render the book icon (usually the classes are pretty easy to guess), and then I would add it to an element like this:

    
      <p class="fa fa-book"></p>
      

    Let’s put some icons in our navbar and jumbotron:

    
      <ul class="nav navbar-nav">
        <li><a href="/"><span class="fa fa-home"></span> Home</a></li>
        <li><a href="users"><span class="fa fa-users"></span> Users</a></li>
        <li><a href="pets"><span class="fa fa-paw"></span> Pets</a></li>
      </ul>
    
      .
      .
      .
    
      <div class="jumbotron">
        <h1><span class="fa fa-paw"></span> Pet App</h1>
        {% block content %}{% endblock %}
      </div>
      

    If everything has gone smoothly to this point, our app should currently look like this in the browser:

    That looks so much better than default un-styled html! Later in the tutorial we will take the style to new heights, but for now it will do. Here is the complete layout.html code:

    
      <!doctype html>
      <html>
          <head>
              <title>Pet App</title>
              
              <script src="https://code.jquery.com/jquery-1.12.3.min.js" integrity="sha256-aaODHAgvwQW1bFOGXMeX+pC4PZIPsvn2h1sArYOhgXQ=" crossorigin="anonymous"></script>
              
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
              
              <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
              
              <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
         
             <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
          
          </head>
          <body>
    
              <nav class="navbar navbar-default navbar-static-top">
                <div class="container">
                  <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                      <span class="sr-only">Toggle navigation</span>
                      <span class="icon-bar"></span>
                      <span class="icon-bar"></span>
                      <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="/">Pet App</a>
                  </div>
                  <div id="navbar" class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                      <li><a href="/"><span class="fa fa-home"></span> Home</a></li>
                      <li><a href="users"><span class="fa fa-users"></span> Users</a></li>
                      <li><a href="pets"><span class="fa fa-paw"></span> Pets</a></li>
                    </ul>
                  </div>
                </div>
              </nav>
          
          
              <div class="container">
                <div class="jumbotron">
                  <h1><span class="fa fa-paw"></span> Pet App</h1>
                  {% block content %}{% endblock %}
                </div>
              </div>
              
          </body>
      </html>
      

    Wrapping up

    So now we know how to set up a basic Flask app! That’s exciting. :smiley: The next step is to create a Postgresql database so that we can store information about our pets and users, and load that information into our html files. We will be covering that in part 4. I hope this tutorial is useful for some people, thanks for reading!