Building REST API for data visualization with Flask

11 Feb

Lately I’m working on a project that involves gathering data in one way or another and then visualizing it using a chart. Up to now I have created a web page with a chart on it, and it wasn’t easy. First I decided I’ll build the chart myself, then I snapped back to reality and built a web component with Chart.js to replace my excuse for a chart. Now I want to make this page dynamic and actually provide some data to this chart. So here I’ll build a REST API for it using Flask.

Requesting data from a REST API

Let’s start this task with a slight deviation. Since I already have some kind of UI for this API I would like to use it to see if I’m doing well or not. This means I need the UI to request the data from the API and then visualize it.

I could do this last of course, but I prefer doing this first, to shorten the feedback loop. So what do I need to do?

Fetch the data

Naturally, I need some data to load. The only way I know how to do this is to ask the back-end to give me some. This back end is going to be our API, so in effect I’m going to make a call to it.

There are many npm packages which are allowing you to do an http call from a web client, and of course there’s the Ajax calls that are a standard. However I prefer the newer standard Fetch API which is available in every modern browser.

Fetching the data is pretty simple really. It all starts with calling the fetch function. Let’s try doing it like that:

const dataPromise = fetch('/data');

The fetch() method takes at least the URI to the resource we need. You can see that I have only written the last part of a valid URI. The reason is, that fetch() considers this resource to be on the same server from where this web page is served. So if your URL is ‘https://example.com’, fetch() would append this URL in front of the resource path you give it here.

This call would return us a promise. Promises are a complex beast, but the easiest way to deal with them is to use an async function and to await the result. Then we can set it to our chart:

async function fetchData() {
  return await fetch('/data');
}

The problem with this function is that since itself is asynchronous, it will return a promise, not whatever is behind the return keyword. The return actually designates the value this promise resolves to. So to break this cycle of promises, we need to set the data to the chart in this function itself. Perhaps it’s better to rename the function to loadData().

Pass the data to the chart

Let’s try to amend the function above:

async function loadData(chart) {
  const dataResponse = await fetch('/data');
  chart.setAttribute(dataResponse.text());
}

Let’s imagine the parameter chart is the web component I built before. It requires the data to be set to its data attribute and this attribute is in the form of a string.

The data itself we can get from the object which the promise from fetch resolves to. It has a bunch of handful methods, but the one we need is text(), which gives us the response from the back-end as string. Which reminds me it is time to create this back-end.

Creating the Flask Application

Following the Flask Quickstart page I can quickly make the root page show something:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

And I can run the server with:

flask --app hello run

So I’ll save my new Flask application under app.py and run the command above. What I get is:

Usage: flask run [OPTIONS]
Try 'flask run --help' for help.

Error: Could not import 'hello'.

Well, I guess the file needs to be called hello.py. And then I’m greeted with this wonderful message:

 * Serving Flask app 'hello'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit

Great! This means we have a server running and we can access it at http://127.0.0.1:5000! I’ll spin up a browser and check what’s there.

It’s not a lot, but we have the “Hello World” that was expected! Hurray! We have a Flask application! Let’s do something useful with it.

Defining the REST API

One of the most important things that has to be done when you’re devising a REST API is to decide what the resources would look like. This is basically the names of your functions, so they have to be pretty, easy to understand, somewhat short and very, very stable. If you ever change one part of the URI for a resource, someone’s code is going to break.

Fortunately, we already defined our resource in the front-end part above, so there’s no need to worry about things like this. We just have to implement the /data resource. So let’s build the function that would process this call.

Build the request handler

First, let’s write some Python code and return a string to the caller. The string I’ll take from the web component article and it’ll be the JSON passed to the Chart constructor.

def data():
    return '''{
        "type": "line",
        "data": {
          "labels": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ],
          "datasets": [
            {
              "label": "dataset 1",
              "data": [{ "x": 0, "y1":2, "y2":1 },{ "x": 1,"y1":2}, { "x": 1.4,"y1":6 }, { "x":2,"y1":3 }, { "x":3,"y1":6 }, {"x":5, "y2": 2}, {"x":6, "y2":1}, {"x":8, "y2":6}, {"x":9,"y2":6}, {"x":10,"y2":0}],
              "parsing": {
                "yAxisKey": "y1"
              },
              "borderColor": "rgb(75,192,192)"
            },
            {
              "label": "dataset 2",
              "data": [{ "x": 0, "y1":20, "y2":1 },{ "x": 1,"y1":2}, { "x": 1.4,"y1":6 }, { "x":2,"y1":3 }, { "x":3,"y1":6 }, {"x":5, "y2": 2}, {"x":6, "y2":1}, {"x":8, "y2":6}, {"x":9,"y2":6}, {"x":10,"y2":0}],
              "parsing": {
                "yAxisKey": "y2"
              },
              "borderColor": "rgb(192,192, 75)"
            }
          ]
        }
      }'''

Route the requests to the handler

Now we have our data ready to be served, we only need to call this method when there is a request for the /data resource. Just slap an @app.route decorator on top of the method like this:

@app.route("/data")
def data():
  ...

And this is enough to receive the data once we navigate to http://localhost:5000/data. So our REST API is up and running. But when I open the web page with the chart I created for testing I see only a form, no chart. There should be a chart here, but it doesn’t show:

No data is visualized yet, but there is an error in the console.

Allowing Fetch to use REST APIs with different origin

This is a bit of a side topic but it completes the picture. Since I’m building the UI above with React I’m running a server on localhost:3000. For the REST API I run a development Flask server on localhost:5000. While they are both running on my local machine, the fact that they’re two separate servers is enough to confuse fetch and send the REST API call to the UI server.

To fix this I can simply add the full address of the /data resource:

async function fetchData() {
  return await fetch('http://localhost:5000/data');
}

This would, however, fail since I’m calling a different server from origin. In fact, it fails with this particular error in the web browser console:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:5000/data. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

CORS setup for our Frontend

CORS stands for Cross-Origin Resource Sharing. It is by default disabled, because Cross-Origin requests can be exploited for a cross-site scripting attacks. But sometimes people do need to access multiple servers for a single web page, so there is a solution to this too.

First’ let’s make our fetch send requests in ‘cors’ mode by passing an option to it:

  const response = await fetch('http://127.0.0.1:5000/data', {method: 'GET', mode: 'cors'});

Now let’s see what happens in the console:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:5000/data. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200. [Learn More]

Enabling CORS on the Backend

The error that I got in the console is there to tell the developer that something has to be done on the backend. It doesn’t explain it well. But that’s what I found out when I googled it.

Therefore I needed some code that puts the ‘Access-Control-Allow-Origin’ header to the response of the Flask App. Something like Flask-CORS. Let’s install it and try it out:

pip install flask-cors

Once installed, I imported it in the hello.py module and initialized it like this:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

This is the simplest way to use Flask-CORS and it allows CORS on all routes. Once this is done, the error message in the console disappears and a beautiful chart appears on the page:

Once CORS is setup the client can access the REST API and the chart appears on the page.

Going Forward

Now that I have a connection between my frontend and the backend, I can focus on writing “business logic” and exposing it through the REST API. This would of course make the Flask app look very ugly if all the end-points are in the single file we created for this server.

To deal with this, you can use Flask-Restful.

If, on the other hand you are trying to present data real-time, perhaps the Server-sent Events approach I described earlier would be more appropriate.

If you wonder how the chart got so big, have a look at Making Canvas greedy using CSS flexbox.