A Series on Flask APIs, Part 1: GETting and POSTing | by Alexis McKenzie | Towards Data Science

This article is part of a series on Flask APIs. This part addresses creating a basic REST API using a JSON doc and running that API locally. Each subsequent part will incorporate new technologies to help you better understand APIs and DevOps in the cloud.
The Basics: What is Flask, What are APIs, What is a REST API?
Flask is a Python microframework commonly used for creating APIs, or Application Programming Interfaces. APIs let you interact with applications. Even modules in Python are a type of API, for this reason: They let you interact with external code in your application. APIs act as a sort of contract between the server and you. As such, have clear documentation for the call formatting and the type of response that is expected.
Flask is a microframework, in contrast to a full-stack framework, that facilitates receiving HTTP requests, processing those requests in a controller, and returning an HTTP response. You can use Flask as a component in a full stack project with Angular or React, but we probably won’t be going into that in this series. Microframeworks typically lack authentication, form validation and database abstraction layers on their own, but those can be built in using other components.
“REST” applies architectural constraints to an API. REST stands for Representational State Transfer and means that when a call is made, a representation of the state is also transferred with that call. These include GET, POST, PUT and DELETE as our verbs, and their usage results in a representation of the server’s data being returned. There are five critical components for your API to be “RESTful”:
A uniform interface that standardizes the request format via our verbs and has logical and patterned URI routes to interact with the APIClient-server separation that allows for the client and server to evolve separately and communicate only via the request URIA stateless request so that each request is self-contained and need not create a server session and the server need not maintain data about past requestsCacheable response data on the server side to improve scalability and client-side performanceAnd finally, a layered system architecture to allow for multiple layers in the application (for authentication typically) and for the client and server to be unaware of which layer they are communicating with
A uniform interface that standardizes the request format via our verbs and has logical and patterned URI routes to interact with the API
Client-server separation that allows for the client and server to evolve separately and communicate only via the request URI
A stateless request so that each request is self-contained and need not create a server session and the server need not maintain data about past requests
Cacheable response data on the server side to improve scalability and client-side performance
And finally, a layered system architecture to allow for multiple layers in the application (for authentication typically) and for the client and server to be unaware of which layer they are communicating with
An optional sixth constraint for code on demand allows the server to respond with code instead of a representation of the data in JSON or XML format.
If we miss any of the key components, our API is simply REST-like. For more information on RESTful APIs, check out the Roy Fielding dissertation.
Prerequisites: You must have python and Flask installed.
Create a local folder for your flask API and open up said folder in VS Code. You may also want to put this in a git repo from the beginning. It will make the subsequent parts of this series easier.Create two folders and a py file as below. Name the folders static and templates. Flask is specifically looking for folders with these names, so a functional approach to folder structures will save you headaches in the future.
Create a local folder for your flask API and open up said folder in VS Code. You may also want to put this in a git repo from the beginning. It will make the subsequent parts of this series easier.
Create two folders and a py file as below. Name the folders static and templates. Flask is specifically looking for folders with these names, so a functional approach to folder structures will save you headaches in the future.
3. Open up the demo_api.py file and write the following code:
It is critical you have the shebang on Line 1. The shebang is an interpreter directive that tells the program loader that this file should be interpreted as a Python file, and /usr/bin/env represents the logical path for where Python is installed. Excluding the shebang will likely not cause problems now, but it will when you try to run this in the cloud.
The route decorator on Line 9 is a feature of Flask that lets us easily create endpoints for our API. In general, a decorator lets us extend the behavior of a function.
The render_template function on Line 14 takes the text variable on Line 11 and renders it in the index.html file.
4*. Next, create a file called index.html in the templates folder with the below code:
<!DOCTYPE html><html lang="en"><head><title>Flask Demo</title></head><body>{{html_page_text}}</body></html>
The double curly braces house the variable referenced in the render_template function in the Python file.
If you need more info on html, check out W3Schools.
*You do not need to create an html file, and the code snippets will tell you how to complete this API with or without.
5. Open up a terminal/command prompt and cd into the directory with your demo_api.py file. Next, type:
$ export FLASK_APP=demo_api$ flask run
You should see the following output in you terminal/command prompt:
6. Go to https://127.0.0.1:5000 . This is the localhost on port 5000.
Localhost, or 127.0.0.1, is a reference to your home computer. No websites can have an IP address that starts with 127 since those IPs are reserved for loopbacks, which let you interact with your computer’s simulation of a webserver. We are able to test applications in localhost because the computer simulates a webserver when a loopback is triggered.
In this section, we’re going to expand the functionality of the API to show how to GET and POST data to a file. Let’s imagine a business scenario where we’re logging and retrieving yearly events. Let’s say we have three objectives: We want to go to /all and see all the events for all time, we want to go to /year/<year> and see events for that particular year, and we want to add events in case some important ones are missing.
Any time you have a route with an unspecified method, Flask assumes the method is GET (that the client is requesting data from the server).
You will need to create a file with data in it. First, create a folder called data and create a file within that called data.json . Paste the following into data.json:
{"events":[{ "year":"2020","id":1,"event_category":"pandemic","event":"covid"},{ "year":"2020","id":2,"event_category":"environmental","event":"west coast wildfires"},{ "year":"2021","id":1,"event_category":"sports","event":"summer olympics"}]}
Let’s start with adding an /all route . We need to create another route and function in our demo_api.py file that reads from the data.json file:
The json_url variable on Line 18 is referencing the path where the data is located. We then use json.loads to open the file and render it inside the index.html page. Note that we can pass all sorts of different data into one template as long as we pay attention to the variables.
Make sure this works as expected for you before progressing.
Now, we are going to work on the /year/<year> route. Check out this video for some thoughts around endpoint naming. First, create a file in templates called events.html. We can no longer use the index.html file because we have multiple items in our events list in the data file. Paste the following code into events.html:
<!DOCTYPE html><html lang="en"><head><title>Flask Demo</title></head><body>{% for item in html_page_text %}<p>{{ item}}</p>{% endfor %}</body></html>
Next, we need to create a new route to get data by year. The full code should look like below:
Notice on Line 24 I included methods =[‘GET’] even though we didn’t do that for any other routes in the past. If a route is only getting data, you do not need to specify the methods, but we will be expanding the methods shortly.
Line 31 is making sure we access the data inside the events key in the data.json file. Line 32 gets the data passed into the route and converts it to a variable. We then iterate through all the data in that list to get the data for the year of interest using a list comprehension on Line 33.
Now you can pass any year into the path and the data in data.json will be filtered to only show events for that year. The for-loop in the events.html is critical to display all events for years with multiple events listed. If you use index.html on Line 36 instead you will see that only one event for each year is returned.
If you go to http://127.0.0.1:5000/year/2020 in your browser, you should see the following results:
Now we are going to handle adding new events to the data.json file. Create another html file under the templates folder called form.html. In order to POST, we need some type of form to send data (or we could also use Postman and pass data in parameters, but more on that later). Paste the following into form.html:
<!DOCTYPE html><html lang="en"><head><title>Flask Demo</title></head><body><form action="#" method="post" ><p>Yearly Events:</p><table><tbody><tr><td>Category:</td><td><input type="text" name="category" /></td></tr><tr><td>Event:</td><td><input type="text" name="event" /></td></tr></tbody></table><p><input type="submit" value="submit"/></p></form></body></html>
First, we need to render this form in our application. Adding the /add route as below will let us do that.
Everything in the /add route that starts on Line 25 should be familiar to you except the send_file function. This lets us send the contents in the form to a client. However, at this point, without anything to receive and process POSTs, you will get an internal server error.
We now need to write some logic to get the POSTed data from the form. There are several ways to do this, but I will show you one approach. Feel free to include other approaches in the comments below.
We need to specify that this accepts the POST method on Line 1. If not, none of the code will work.
Lines 15 through 20 pull the data out of the form that’s posted. The code is referencing those elements by the names specified in the form.html attributes. Lines 21 through 25 put that data into a dictionary so all the data is retrieved from one form submittal is together.
Lines 27 through 30 are a bit more complex based on how the data file is formatted, but I will walk you through my thought process based on how the data is structured. There is great documentation for Python built-in functions like open, but I chose r+ as the mode to make sure the file could be read and updated.
First, we need to get the json changes to work as we envisioned. Initially, I thought, OK, I need to load the data currently in the file, append the new data to the end of the events list, and then overwrite the original file. If you attempt to add another value, you will notice this doesn’t exactly work...the new dictionary gets added to the end of the old dictionary:
So we need to start at the beginning of the file instead of appending to the end. That’s where the seek function comes in. This points us to the beginning of the file instead of the end (where the data was previously getting appended)
Now you have a basic API that can GET and POST data from a form to a document!
Notes/Troubleshooting: If you run the above code, you may notice that the first line gets overwritten. If your original data.json had some indents, this may be problematic. Here is where a useful argument in json.dump comes in handy. Indent!
You can use any level of indentation from 0 up, but you will need to play around with it depending on how the data.json file was indented.
Some other approaches: You can use additional modes in the built-in open function to first read and then overwrite the file. If you have additional suggestions or comments, please post in the comments section.
Thanks for reading!