Ep.10 Flask and the Fetch API

Flask and the Fetch API | Learning Flask Ep. 10

Asynchronous requests from the client to the server using the JavaScript Fetch API

Making asynchronus requests from the client to the server is a common feature of most modern web applications, allowing a more fluid user experience.

In this episode of the “Learning Flask” series, you’ll learn how to make asynchronus requests from the client to your application using some basic vanilla JavaScript and the Fetch API.

The Fetch API supersedes XML AJAX requests, allowing a relitively clean and simple promise based way to post and fetch data to and from the server, with a simple yet powerful feature set for catching errors and bad requests.

Create a new route

For this example, we’re going to be building a simple guestbook, allowing our users to post their name and message along with reading other previous entries.

We’ll use the Fetch API to post a new entry and a JavaScript function to render the entries to our template.

Let’s start with a new route called /guestbook:

app/app/views.py

@app.route("/guestbook")
def guestbook():
    return render_template("public/guestbook.html")

We’ll need to create a new template too. Go ahead and create guestbook.html in the public directory and add the following:

app/app/templates/public/guestbook.html

{% extends "public/templates/public_template.html" %}

{% block title %}Guestbook{% endblock %}

{% block main %}

<div class="container">
  <div class="row">
    <div class="col">

      <h1>Guestbook</h1>
      <hr>

    </div>
  </div>
</div>

{% endblock %}

Save the files and head to /guestbook in the browser to see the new template.

Note - We’re using the Bootstrap 4 library for CSS

We’ll need some input fields for our users to fill out the guestbook.

We’ll add an input field for name and a textarea for message along with a button to submit their entry:

app/app/templates/public/guestbook.html

{% extends "public/templates/public_template.html" %}

{% block title %}Guestbook{% endblock %}

{% block main %}

<div class="container">
  <div class="row">
    <div class="col">

      <h1>Guestbook</h1>
      <hr>

      <div class="mb-3">
        <div class="form-group">
          <label>Name</label>
          <input type="text" class="form-control" id="name" placeholder="Your name">
        </div>

        <div class="form-group">
          <label>Message</label>
          <textarea class="form-control" id="message" cols="30" rows="3" placeholder="Your message"></textarea>
        </div>

        <button class="btn btn-primary" id="submit" onclick="submit_message();">Submit message</button>
      </div>

      <h3>Messages</h3>
      <hr>

      <div class="mb-3" id="messages"></div>

    </div>
  </div>
</div>

{% endblock %}

{% block script %}

Notice we’ve set an id attribute on the input elements and the button. We’ll create a function to submit the data when the button is clicked, collecting the values from the input fields and posting it to our Flask app.

We’ve also added a new heading and a <div> element with the id messages. We’ll use this div to display the entries of our guestbook later.

Collecting JSON

We’re going to wrap all of our JavaScript in a set of {% block script %} {% endblock %} tags with corresponding {% block script %} {% endblock %} tags in our base template.

Let’s create a function to collect the input values and create a new JSON object when the button is clicked:

app/app/templates/public/guestbook.html

{% block script %}

<script>

    function submit_message() {

        var name = document.getElementById("name");
        var message = document.getElementById("message");

        var entry = {
            name: name.value,
            message: message.value
        };
    }

</script>

{% endblock %}

We need to attach this function to the submit button. Go ahead and refactor the <button> element to the following:

<button class="btn btn-primary" id="submit" onclick="submit_message();">Submit message</button>

We’re calling the submit_message() function when someone clicks the button, fetching the input values and storing them in a JSON object called entry.

Creating a JSON handler in Flask

Before we attempt to post any data to our app, we need to create a handler for it.

We’re submitting data to our app so we’ll need to import request from flask.

We’re also going to be creating a JSON response so go ahead and import jsonify from flask too.

We will also create a response object, so go ahead and import make_response from flask.

app/app/views.py

from flask import request, jsonify, make_response` 

Go ahead and create the following route in your Flask app:

app/app/views.py

@app.route("/guestbook/create-entry", methods=["POST"])
def create_entry():

    req = request.get_json()

    print(req)

    res = make_response(jsonify({"message": "OK"}), 200)

    return res

For now, we’ll just use the print() function to print our request data to the terminal and return a simple JSON response.

Fetch setup

Let’s go back to guestbook.html and work on the submit_message() function to post some data to our app.

Our fetch function is initially going to look like this:

fetch(${window.origin}/guestbook/create-entry, {
    method: "POST",
    credentials: "include",
    body: JSON.stringify(entry),
    cache: "no-cache",
    headers: new Headers({
      "content-type": "application/json"
    })
  })
  .then(function(response) {
    if (response.status !== 200) {
      console.log(`Looks like there was a problem. Status code: ${response.status}`);
      return;
    }
    response.json().then(function(data) {
      console.log(data);
    });
  })
  .catch(function(error) {
    console.log("Fetch error: " + error);
});

Let’s take a look at our JavaScript so far and take a quick look at what we’re doing:

{% block script %}

<script>

  function submit_message() {

    var name = document.getElementById("name");
    var message = document.getElementById("message");

    var entry = {
      name: name.value,
      message: message.value
    };

    fetch(`${window.origin}/guestbook/create-entry`, {
      method: "POST",
      credentials: "include",
      body: JSON.stringify(entry),
      cache: "no-cache",
      headers: new Headers({
        "content-type": "application/json"
      })
    })
      .then(function (response) {
        if (response.status !== 200) {
          console.log(`Looks like there was a problem. Status code: ${response.status}`);
          return;
        }
        response.json().then(function (data) {
          console.log(data);
        });
      })
      .catch(function (error) {
        console.log("Fetch error: " + error);
      });

  }

</script>

{% endblock %}

Fetch explained

fetch() takes 2 arguments, a URL or input and a set of options or init as descibed in the fetch documentation.

We’ve provided the URL to our Flask JSON handler using ${window.origin} followed by our URL, along with an init object containing several keys and values to setup the type of request we want to make. In our case:

  • method: "POST" As we’re posting data to the server
  • credentials: "include" To send any cookies from the current domain/client to the server
  • body: JSON.stringify(entry) Converts our JSON object into a string
  • cache: "no-cache" We’re not interested in any cached data
  • headers: new Headers({"content-type": "application/json"}) Adds a header to tell the server we’re sending JSON

You’ll then see we’ve got .then() chained to our fetch() request which does some error handling for us, depending on what status code the server responds with. We’re going to leave that as is for now.

response.json() parses returned JSON from a string to a JSON object which we can access with the data variable passed into the callback function chained to it using .then()! 😅

The final block is a .catch() containing a callback function to catch and handle any errors with our fetch request.

Tip - The more you use fetch the more it starts to make sense!

Let’s post some data to our route.

Posting with Fetch

Save the file, reload the browser, fill out the name and message fields and submit the form. You should see a dictionary printed in your terminal along with a JSON response printed to the browser console.

Let’s refactor the code in our view to return the same object send to us from the client:

app/app/views.py

@app.route("/guestbook/create-entry", methods=["POST"])
def create_entry():

    req = request.get_json()

    print(req)

    res = make_response(jsonify(req), 200)

    return res

All we do it pass the req variable to jsonify(), which will serialize the Python dictionary into a JSON string.

If you now fill out the form and send another request to the app, you’ll see down in the developer tools console our route is now just bouncing back what was sent to it!

Takeaways

The main takeaway from this episode is that asynchronously sending and receiving data betwen the client and the server is relitively simple using the Fetch API.

In the next part of this series, we’ll complete our guestbook app where you’ll see more of the Fetch API in action! We’ll be saving entries and reloading new entries in the background without having to reload the page.

Last modified · 28 Feb 2019

Written with StackEdit.