Building Flask REST APIs

Python REST APIs With Flask, Connexion, and SQLAlchemy – Part 4

by Doug Farrell Oct 30, 2019 flask front-end intermediate web-dev
Tweet Share Email

In Part 3 of this series, you added relationships to the REST API and to the database that supports it. This gave you a powerful tool you can use to build interesting programs that send persistent data, as well as the relationships between that data, to a database system. Having a REST API gives you the ability to create a Single-Page Application (SPA) with HTML, CSS, and JavaScript. This is a good starting point before you make the jump to more powerful front-end frameworks, like Angular or React.

In this article, you’ll learn how to:

  • Structure an HTML file to act as the template of a single-page web application
  • Use Cascading Style Sheets (CSS) to style the presentation of an application
  • Use native JavaScript to add interactivity to an application
  • Use JavaScript to make HTTP AJAX requests to the REST API you developed in Part 3 of this series

You can get all of the code you’ll see in this tutorial at the link below:

Who This Article Is For

Part 1 of this series guided you through building a REST API, and Part 2 showed you how to connect that REST API to a database. In Part 3, you added relationships to the REST API and the supporting database.

This article is about presenting that REST API to a user as a browser-based web application. This combination gives you both front-end and back-end abilities, which is a useful and powerful skill set.

Creating Single-Page Applications

In Part 3 of this series, you added relationships to the REST API and database to represent notes associated with people. In other words, you created a kind of mini-blog. The web applications you built in part 3 showed you one way to present and interact with the REST API. You navigated between three Single-Page Applications (SPA) to access different parts of the REST API.

While you could have combined that functionality into a single SPA, that approach would make the concepts of styling and interactivity more complex, without much added value. For that reason, each page was a complete, standalone SPA.

In this article, you’ll focus on the People SPA, which presents the list of people in the database and provides an editor feature to create new people and update or delete existing ones. The Home and Notes pages are conceptually similar.

What Existing Frameworks Are There?

There are existing libraries that provide inbuilt and robust functionality for creating SPA systems. For example, the Bootstrap library provides a popular framework of styles for creating consistent and good-looking web applications. It has JavaScript extensions that add interactivity to the styled DOM elements.

There are also powerful web application frameworks, like React and Angular, that give you complete web application development systems. These are useful when you want to create large, multi-page SPAs that would be cumbersome to build from scratch.

Why Build Your Own?

With the availability of tools like those listed above, why would you choose to create an SPA from scratch? Take Bootstrap, for instance. You can use it to create SPAs that look excellent, and you can certainly use it with your JavaScript code!

The problem is that Bootstrap has a steep learning curve you’ll need to climb if you want to use it well. It also adds a lot of Bootstrap-specific attributes to the DOM elements defined in your HTML content. Likewise, tools like React and Angular also have significant learning curves you’ll need to overcome. However, there’s still a place for web applications that don’t rely on tools like these.

Often, when you’re building a web application, you want to build a proof-of-concept first to see if the application is at all useful. You’ll want to get this up and running quickly, so it can be faster for you to roll your own prototype and upgrade it later. Since you won’t invest much time in the prototype, it won’t be too costly to start over and create a new application with a supported, fully-featured framework.

There’s a gap between what you’re going to develop with the People app in this article and what you could build with a complete framework. It’s up to you to decide where the tipping point is between providing the functionality yourself or adopting a framework.

Parts of Single-Page Applications

There are a few main forms of interactivity in traditional web-based systems. You can navigate between pages and submit a page with new information. You can fill out forms containing input fields, radio buttons, checkboxes, and more. When you perform these activities, the webserver responds by sending new files to your browser. Then, your browser renders the content again.

Single-page applications break this pattern by loading everything they need first. Then, any interactivity or navigation is handled by JavaScript or by calls to the server behind the scenes. These activities update the page content dynamically.

There are three major components of a single-page application:

  1. HTML provides the content of a web page, or what is rendered by your browser.
  2. CSS provides the presentation, or style, of a web page. It defines how the content of the page should look when rendered by your browser.
  3. JavaScript provides the interactivity of a web page. It also handles communication with the back-end server.

Next, you’ll take a closer look at each of these major components.

HTML

HTML is a text file delivered to your browser that provides the primary content and structure for a single-page application. This structure includes the definitions for id and class attributes, which are used by CSS to style the content and JavaScript to interact with the structure. Your browser parses the HTML file to create the Document Object Model (DOM), which it uses to render the content to the display.

The markup within an HTML file includes tags, like paragraph tags <p>...</p> and header tags <h1>...</h1>. These tags become elements within the DOM as your browser parses the HTML and renders it to the display. The HTML file also contains links to external resources that your browser will load as it parses the HTML. For the SPA you’re building in this article, these external resources are CSS and JavaScript files.

CSS

Cascading Style Sheets (CSS) are files that contain styling information that will be applied to whatever DOM structure is rendered from an HTML file. In this way, the content of a web page can be separated from its presentation.

In CSS, the style for a DOM structure is determined by selectors. A selector is just a method of matching a style to elements within the DOM. For example, the p selector in the code block below applies styling information to all paragraph elements:

p {
    font-weight: bold;
    background-color: cyan;
}

The above style will apply to all paragraph elements in the DOM. The text will appear as bold and have a background color of cyan.

The cascading part of CSS means that styles defined later, or in a CSS file loaded after another, will take precedence over any previously defined style. For example, you can define a second paragraph style after the style above:

p {
    font-weight: bold;
    background-color: cyan;
}

p {
    background-color: cornflower;
}

This new style definition would modify the existing style so that all paragraph elements in the DOM will have a background color of cornflower. This overrides the background-color of the previous style, but it leaves the font-weight setting intact. You could also define the new paragraph style in a CSS file of its own.

The id and class attributes let you apply a style to specific individual elements in the DOM. For example, the HTML to render a new DOM might look like this:

<p>
    This is some introductory text
</p>

<p class="panel">
    This is some text contained within a panel
</p>

This will create two paragraph elements within the DOM. The first has no class attribute, but the second has a class attribute of panel. Then, you can create a CSS style like this:

p {
    font-weight: bold;
    width: 80%;
    margin-left: auto;
    margin-right: auto;
    background-color: lightgrey;
}

.panel {
    border: 1px solid darkgrey;
    border-radius: 4px;
    padding: 10px;
    background-color: lightskyblue;
}

Here, you define a style for any elements that have the panel attribute. When your browser renders the DOM, the two paragraph elements should look like this:

people paragraphs example

Both paragraph elements have the first style definition applied to them because the p selector selects them both. But only the second paragraph has the .panel style applied to it because it’s the only element with the class attribute panel that matches that selector. The second paragraph gets new styling information from the .panel style, and overrides the background-color style defined in the p style.

JavaScript

JavaScript provides all of the interactive features for an SPA, as well as dynamic communication with the REST API provided by the server. It also performs all of the updates to the DOM, allowing an SPA to act much like a full Graphical User Interface (GUI) application like Word or Excel.

As JavaScript has evolved, it’s become easier and more consistent to work with the DOM provided by modern browsers. You’ll be using a few conventions, like namespaces and separation of concerns, to help keep your JavaScript code from conflicting with other libraries you might include.

Modules and Namespaces

You might already know about namespaces in Python and why they’re valuable. In short, namespaces give you a way to keep the names in your program unique to prevent conflicts. For example, if you wanted to use log() from both the math and cmath modules, then your code might look something like this:

>>>
>>> import math
>>> import cmath
>>> math.log(10)
2.302585092994046
>>> cmath.log(10)
(2.302585092994046+0j)

The Python code above imports both the math and cmath modules, then calls log(10) from each module. The first call returns a real number and the second returns a complex number, which cmath has functions for. Each instance of log() is unique to its own namespace (math or cmath), meaning the calls to log() don’t conflict with each other.

Modern JavaScript has the ability to import modules and assign namespaces to those modules. This is useful if you need to import other JavaScript libraries where there might be a name conflict.

If you look at the end of the people.js file, then you’ll see this:

301 // Create the MVC components
302 const model = new Model();
303 const view = new View();
304 const controller = new Controller(model, view);
305 
306 // Export the MVC components as the default
307 export default {
308     model,
309     view,
310     controller
311 };

The code above creates the three components of the MVC system, which you’ll see later on in this article. The default export from the module is a JavaScript literal object. You import this module at the bottom of the people.html file:

50 <script type="module">
51     // Give the imported MVC components a namespace
52     import * as MVC from "/static/js/people.js";
53 
54     // Create an intentional global variable referencing the import
55     window.mvc = MVC;
56 </script>

Here’s how this code works:

  • Line 50 uses type="module" to tell the system that the file is a module and not just a JavaScript file.

  • Line 52 imports the default object from people.js and assigns it the name MVC. This creates a namespace called MVC. You can give the imported object any name that doesn’t conflict with other JavaScript libraries you might not have control over.

  • Line 55 creates a global variable, which is a convenient step. You can use this to inspect the mvc object with a JavaScript debugger and look at model, view, and controller.

Naming Conventions

For the most part, the JavaScript code you’re using here is in camel case. This naming convention is widely used in the JavaScript community, so the code examples reflect that. However, your Python code will use snake case, which is more conventional in the Python community.

This difference in naming can be confusing where your JavaScript code interacts with Python code, and especially where shared variables enter the REST API interface. Be sure to keep these differences in mind as you write your code.

Separation of Concerns

The code that drives an SPA can be complicated. You can use the Model–View–Controller (MVC) architectural pattern to simplify things by creating a separation of concerns. The Home, People, and Notes SPAs use the following MVC pattern:

  • The Model provides all access to the server REST API. Anything presented on the display comes from the model. Any changes to the data go through the model and back to the REST API.

  • The View controls all display handling and DOM updates. The view is the only part of the SPA that interacts with the DOM and causes the browser to render and re-render any changes to the display.

  • The Controller handles all user interaction and any user data entered, like click events. Because the controller reacts to user input, it also interacts with the Model and View based on that user input.

Here’s a visual representation of the MVC concept as implemented in the SPA code:

JavaScript MVC Diagram
Model / View / Controller

In the illustration above, the Controller has a strong connection with both the Model and the View. Again, this is because any user interaction the Controller handles might require reaching out to the REST API to get or update data. It may even require updating the display.

The dotted line that goes from the Model to the Controller indicates a weak connection. Because calls to the REST API are asynchronous, the data that the Model provides to the Controller returns at a later time.

Creating the People SPA

Your mini-blog demonstration app has pages for Home, People, and Notes. Each of these pages is a complete, standalone SPA. They all use the same design and structure, so even though you’re focusing on the People application here, you’ll understand how to construct all of them.

People HTML

The Python Flask web framework provides the Jinja2 templating engine, which you’ll use for the People SPA. There are parts of the SPA that are common to all three pages, so each page uses the Jinja2 template inheritance feature to share those common elements.

You’ll provide the HTML content for the People SPA in two files: parent.html and people.html files. You can get the code for these files at the link below:

Here’s what your parent.html will look like:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     {% block head %}
 6     <title>{% block title %}{% endblock %} Page</title>
 7     {% endblock %}
 8 </head>
 9 <body>
10 <div class="navigation">
11     <span class="buttons">
12         <a href="/">Home</a>
13         <a href="/people">People</a>
14     </span>
15     <span class="page_name">
16         <div></div>
17     </span>
18     <span class="spacer"></span>
19 </div>
20 
21 {% block body %}
22 {% endblock %}
23 </body>
24 
25 {% block javascript %}
26 {% endblock %}
27 
28 </html>

parent.html has a few major elements:

  • Line 1 sets the document type as <!DOCTYPE html>. All new HTML pages begin with this declaration. Modern browsers know this means to use the HTML 5 standard, while older browsers will fall back to the latest standard they can support.
  • Line 4 tells the browser to use UTF-8 encoding.
  • Lines 10 to 19 define the navigation bar.
  • Lines 21 and 22 are Jinja2 block markers, which will be replaced by content in people.html.
  • Lines 25 and 26 are Jinja2 block markers that act as a placeholder for JavaScript code.

The people.html file will inherit the parent.html code. You can expand the code block below to see the whole file:

 1 {% extends "parent.html" %}
 2 {% block title %}People{% endblock %}
 3 {% block head %}
 4 {% endblock %}
 5 {% block page_name %}Person Create/Update/Delete Page{% endblock %}
 6 
 7 {% block body %}
 8     <div class="container">
 9         <input id="url_person_id" type="hidden" value="{{ person_id }}" />
10         <div class="section editor">
11             <div>
12                 <span>Person ID:</span>
13                 <span id="person_id"></span>
14             </div>
15             <label for="fname">First Name
16                 <input id="fname" type="text" />
17             </label>
18             <br />
19             <label for="lname">Last Name
20                 <input id="lname" type="text" />
21             </label>
22             <br />
23             <button id="create">Create</button>
24             <button id="update">Update</button>
25             <button id="delete">Delete</button>
26             <button id="reset">Reset</button>
27         </div>
28         <div class="people">
29             <table>
30                 <caption>People</caption>
31                 <thead>
32                     <tr>
33                         <th>Creation/Update Timestamp</th>
34                         <th>Person</th>
35                     </tr>
36                 </thead>
37             </table>
38         </div>
39         <div class="error">
40         </div>
41     </div>
42     <div class="error">
43     </div>
44 
45 {% endblock %}

people.html has just two major differences:

  • Line 1 tells Jinja2 that this template inherits from the parent.html template.
  • Lines 7 to 45 create the body of the page. This includes the editing section and an empty table to present the list of people. This is the content inserted in the {% block body %}{% endblock %} section of the parent.html file.

The HTML page generated by parent.html and people.html contains no styling information. Instead, the page is rendered in the default style of whatever browser you use to view it. Here’s what your app looks like when rendered by the Chrome browser:

people page before styling and javascript
People HTML page before styling

It doesn’t look much like a Single-Page Application! Let’s see what you can do about that.

People CSS

To style the People SPA, you first need to add the normalize.css style sheet. This will make sure that all browsers consistently render elements more closely to HTML 5 standards. The specific CSS for the People SPA is supplied by two style sheets:

  1. parent.css, which you pull in with parent.html
  2. people.css, which you pull in with people.html

You can get the code for these stylesheets at the link below:

You’ll add both normalize.css and parent.css to the <head>...</head> section of parent.html:

 1 <head>
 2     <meta charset="UTF-8">
 3     {% block head %}
 4     <title>{% block title %}{% endblock %} Page</title>
 5     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css">
 6     <link rel="stylesheet" href="/static/css/parent.css">
 7     {% endblock %}
 8 </head>

Here’s what these new lines do:

  • Line 5 gets normalize.css from a content delivery network (CDN), so you don’t have to download it yourself.
  • Line 6 gets parent.css from your app’s static folder.

For the most part, parent.css sets the styles for the navigation and error elements. It also changes the default font to Google’s Roboto font using these lines:

 5 @import url(http://fonts.googleapis.com/css?family=Roboto:400,300,500,700);
 6 
 7 body, .ui-btn {
 8     font-family: Roboto;
 9 }

You pull in the Roboto font from a Google CDN. Then, you apply this font to all elements in the SPA body that also have a class of .ui-btn.

Likewise, people.css contains styling information specific to the HTML elements that create the People SPA. You add people.css to the people.html file inside the Jinja2 {% block head %} section:

 3 {% block head %}
 4     {{ super() }}
 5     <link rel="stylesheet" href="/static/css/people.css">
 6 {% endblock %}

The file contains a few new lines:

  • Line 2 has a call to {{ super() }}. This tells Jinja2 to include anything that exists in the {% block head %} section of parent.html.
  • Line 3 pulls in the people.css file from your app’s static folder.

After you include the stylesheets, your People SPA will look more like this:

People page after styling, but before JavaScript
People HTML page after styling, but before JavaScript

The People SPA is looking better, but it’s still incomplete. Where are the rows of people data in the table? All the buttons in the editor section are enabled, so why don’t they do anything? You’ll fix these issues in the next section with some JavaScript.

People JavaScript

You’ll pull JavaScript files into the People SPA just like you did with the CSS files. You’ll add the following bit of code to the bottom of people.html file:

48 {% block javascript %}
49 {{ super() }}
50 <script type="module">
51     // Give the imported MVC components a namespace
52     import * as MVC from "/static/js/people.js";
53 
54     // Create an intentional global variable referencing the import
55     window.mvc = MVC;
56 </script>
57 {% endblock %}

Notice the type="module" declaration on the opening <script> tag in line 50. This tells the system that the script is a JavaScript module. The ES6 import syntax will be used to pull the exported parts of the code into the browser context.

The People MVC

All of the SPA pages use a variation of the MVC pattern. Here’s an example implementation in JavaScript:

 1 // Create the MVC components
 2 const model = new Model();
 3 const view = new View();
 4 const controller = new Controller(model, view);
 5 
 6 // Export the MVC components as the default
 7 export default {
 8     model,
 9     view,
10     controller
11 };

This code doesn’t do anything just yet, but you can use it to see the following elements of MVC structure and implementation:

  • Line 2 creates an instance of the Model class and assigns it to model.
  • Line 3 creates an instance of the View class and assigns it to view.
  • Line 4 creates an instance of the Controller class and assigns it to controller. Note that you pass both model and view to the constructor. This is how the controller gets a link to the model and view instance variables.
  • Lines 7 to 11 export a JavaScript literal object as the default export.

Because you pull in people.js at the bottom of people.html, the JavaScript is executed after your browser creates the SPA DOM elements. This means that JavaScript can safely access the elements on the page and begin to interact with the DOM.

Again, the code above doesn’t do anything just yet. To make it work, you’ll need to define your Model, View, and Controller.

People Model

The Model is responsible for communicating with the REST API provided by the Flask server. Any data that comes from the database, and any data the SPA changes or creates, must go through the Model. All communication with the REST API is done using HTTP AJAX calls initiated by JavaScript.

Modern JavaScript provides fetch(), which you can use to make AJAX calls. The code for your Model class implements one AJAX method to read the REST API URL endpoint /api/people and get all the people in the database:

 1 class Model {
 2     async read() {
 3         let options = {
 4             method: "GET",
 5             cache: "no-cache",
 6             headers: {
 7                 "Content-Type": "application/json"
 8                 "accepts": "application/json"
 9             }
10         };
11         // Call the REST endpoint and wait for data
12         let response = await fetch(`/api/people`, options);
13         let data = await response.json();
14         return data;
15     }
16 }

Here’s how this code works:

  • Line 1 defines the class Model. This is what will be exported later as part of the mvc object.

  • Line 2 begins the definition of an asynchronous method called read(). The async keyword in front of read() tells JavaScript that this method performs asynchronous work.

  • Lines 3 to 9 create an options object with parameters for the HTTP call, like the method and what the call expects for data.

  • Line 12 uses fetch() to make an asynchronous HTTP call to the /api/people URL REST endpoint provided by the server. The keyword await in front of fetch() tells JavaScript to wait asynchronously for the call to complete. When this is finished, the results are assigned to response.

  • Line 13 asynchronously converts the JSON string in the response to a JavaScript object and assigns it to data.

  • Line 14 returns the data to the caller.

Essentially, this code tells JavaScript to make a GET HTTP request to /api/people, and that the caller is expecting a Content-Type of application/json and json data. Recall that a GET HTTP call equates to Read in a CRUD-oriented system.

Based on the Connexion configuration defined in swagger.yml, this HTTP call will call def read_all(). This function is defined in people.py and queries the SQLite database to build a list of people to return to the caller. You can get the code for all of these files at the link below:

In the browser, JavaScript executes in a single thread and is intended to respond to user actions. Because of this, it’s a bad idea to block JavaScript execution that’s waiting for something to complete, like an HTTP request to a server.

What if the request went out across a very slow network, or the server itself was down and would never respond? If JavaScript were to block and wait for the HTTP request to complete in these kinds of conditions, then it might finish in seconds, minutes, or perhaps not at all. While JavaScript is blocked, nothing else in the browser would react to user actions!

To prevent this blocking behavior, HTTP requests are executed asynchronously. This means that an HTTP request returns to the event loop immediately before the request completes. The event loop exists in any JavaScript application that runs in the browser. The loop continuously waits for an event to complete so it can run the code associated with that event.

When you place the await keyword before fetch(), you tell the event loop where to return when the HTTP request completes. At that point, the request is complete and any data returned by the call is assigned to response. Then, controller calls this.model.read() to receive the data returned by the method. This creates a weak link with the controller, as the model doesn’t know anything about what called it, just what it returned to that caller.

People View

this.view is responsible for interacting with the DOM, which is shown by the display. It can change, add, and delete items from the DOM, which are then re-rendered to the display. The controller makes calls to the view’s methods to update the display. The View is another JavaScript class with methods the controller can call.

Below is a slightly simplified version of the People SPA’s View class:

 1 class View {
 2     constructor() {
 3         this.table = document.querySelector(".people table");
 4         this.person_id = document.getElementById("person_id");
 5         this.fname = document.getElementById("fname");
 6         this.lname = document.getElementById("lname");
 7     }
 8 
 9     reset() {
10         this.person_id.textContent = "";
11         this.lname.value = "";
12         this.fname.value = "";
13         this.fname.focus();
14     }
15 
16     buildTable(people) {
17         let tbody,
18             html = "";
19 
20         // Iterate over the people and build the table
21         people.forEach((person) => {
22             html += `
23             <tr data-person_id="${person.person_id}" data-fname="${person.fname}" data-lname="${person.lname}">
24                 <td class="timestamp">${person.timestamp}</td>
25                 <td class="name">${person.fname} ${person.lname}</td>
26             </tr>`;
27         });
28         // Is there currently a tbody in the table?
29         if (this.table.tBodies.length !== 0) {
30             this.table.removeChild(this.table.getElementsByTagName("tbody")[0]);
31         }
32         // Update tbody with our new content
33         tbody = this.table.createTBody();
34         tbody.innerHTML = html;
35     }
36 }

Here’s how this code works:

  • Line 1 begins the class definition.

  • Lines 2 to 7 define the class constructor, much like the def __init__(self): definition in a Python class. The constructor is getting elements from the DOM and creating alias variables to use in other parts of the class. The this. in front of those variable names is much like self. in Python. It designates the current instance of the class when used.

  • Lines 9 to 14 define reset(), which you’ll use to set the page back to a default state.

  • Lines 16 to 36 define buildTable(), which builds the table of people based on the people data passed to it.

The alias variables are created to cache the DOM objects returned by calls to document.getElementByID() and document.querySelector(), which are relatively expensive JavaScript operations. This allows quick use of the variables in the other methods of the class.

Let’s take a closer look at build_table(), which is the second method in the View class:

16 buildTable(people) {
17     let tbody,
18         html = "";
19 
20     // Iterate over the people and build the table
21     people.forEach((person) => {
22         html += `
23         <tr data-person_id="${person.person_id}" data-fname="${person.fname}" data-lname="${person.lname}">
24             <td class="timestamp">${person.timestamp}</td>
25             <td class="name">${person.fname} ${person.lname}</td>
26         </tr>`;
27     });
28     // Is there currently a tbody in the table?
29     if (this.table.tBodies.length !== 0) {
30         this.table.removeChild(this.table.getElementsByTagName("tbody")[0]);
31     }
32     // Update tbody with our new content
33     tbody = this.table.createTBody();
34     tbody.innerHTML = html;
35 }

Here’s how the function works:

  • Line 16 creates the method and passes the people variable as a parameter.
  • Lines 21 to 27 iterate over the people data using JavaScript arrow functions to create a function that builds up the table rows in the html variable.
  • Lines 29 to 31 remove any <tbody> elements in the table if they exist.
  • Line 33 creates a new tbody element in the table.
  • Line 34 inserts the html string previously created into the tbody element as HTML.

This function dynamically builds the table in the People SPA from the data passed to it, which is the list of people that came from the /api/people/ REST API call. This data is used along with JavaScript template strings to generate the table rows to insert into the table.

People Controller

The Controller is the central clearinghouse of the MVC implementation, as it coordinates the activity of both model and view. As such, the code to define it is a little more complicated. Here’s a simplified version:

 1 class Controller {
 2     constructor(model, view) {
 3         this.model = model;
 4         this.view = view;
 5 
 6         this.initialize();
 7     }
 8     async initialize() {
 9         await this.initializeTable();
10     }
11     async initializeTable() {
12         try {
13             let urlPersonId = parseInt(document.getElementById("url_person_id").value),
14                 people = await this.model.read();
15 
16             this.view.buildTable(people);
17 
18             // Did we navigate here with a person selected?
19             if (urlPersonId) {
20                 let person = await this.model.readOne(urlPersonId);
21                 this.view.updateEditor(person);
22                 this.view.setButtonState(this.view.EXISTING_NOTE);
23 
24             // Otherwise, nope, so leave the editor blank
25             } else {
26                 this.view.reset();
27                 this.view.setButtonState(this.view.NEW_NOTE);
28             }
29             this.initializeTableEvents();
30         } catch (err) {
31             this.view.errorMessage(err);
32         }
33     }
34     initializeCreateEvent() {
35         document.getElementById("create").addEventListener("click", async (evt) => {
36             let fname = document.getElementById("fname").value,
37                 lname = document.getElementById("lname").value;
38 
39             evt.preventDefault();
40             try {
41                 await this.model.create({
42                     fname: fname,
43                     lname: lname
44                 });
45                 await this.initializeTable();
46             } catch(err) {
47                 this.view.errorMessage(err);
48             }
49         });
50     }
51 }

Here’s how it works:

  • Line 1 begins the definition of the Controller class.

  • Lines 2 to 7 define the class constructor and create the instance variables this.model and this.view with their respective parameters. It also calls this.initialize() to set up the event handling and build the initial table of people.

  • Lines 8 to 10 define initialize() and mark it as an asynchronous method. It calls this.initializeTable() asynchronously and waits for it to complete. This simplified version only includes this one call, but the full version of the code contains other initialization methods used for the rest of the event handling set up.

  • Line 11 defines initializeTable() as an asynchronous method. This is necessary because it calls model.read(), which is also asynchronous.

  • Line 13 declares and initializes the urlPersonId variable with the value of the HTML hidden input url_person_id.

  • Line 14 calls this.model.read() and asynchronously waits for it to return with people data.

  • Line 16 calls this.view.buildTable(people) to fill the HTML table with people data.

  • Lines 19 to 28 determine how to update the editor portion of the page.

  • Line 29 calls this.initializeTableEvents() to install the event handling for the HTML table.

  • Line 31 calls this.view.errorMessage(err) to display errors should they occur.

  • Lines 34 to 49 install a click event handler on the create button. This calls this.model.create(...) to create a new person using the REST API, and updates the HTML table with new data.

The bulk of the controller code is like this, setting event handlers for all the expected events on the People SPA page. The controller continues creating functions in those event handlers to orchestrate calls to this.model and this.view, so that they perform the right actions when those events occur.

When your code is complete, your People SPA page will look like this:

people page after styling and javascript
People HTML page after styling and JavaScript

The content, styling, and functionality are all complete!

Conclusion

You’ve covered a great deal of new ground and should be proud of what you’ve learned! It can be tricky to jump back and forth between Python and JavaScript to create a complete Single-Page Application.

If you keep your content (HTML), presentation (CSS), and interaction (JavaScript) separate, then you can substantially reduce the complexity. You can also make JavaScript coding more manageable by using the MVC pattern to further break down the complexity of user interaction.

You’ve seen how using these tools and ideas can help you create reasonably complex Single-Page Applications. Now you’re better equipped to make decisions about whether to build an app this way, or take the plunge into a larger framework!

You can get all of the code you saw in this tutorial at the link below:

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Doug Farrell

Doug Farrell

Doug is a Python developer with more than 25 years of experience. He writes about Python on his personal website and works as a Senior Web Engineer with Shutterfly.

» More about Doug

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: flask front-end intermediate web-dev