Getting the weather — a full stack ReactJS/Python Flask API tutorial — Part 1

Chris Thornhill
6 min readMar 4, 2020

--

Image Source: Facebook Flash-Prompt Group

Overview

I know, another tutorial on ReactJS, but this one is different — we will be doing the frontend in ReactJS, as well as a Python/Flask API server. And for an extra twist, deploying it in a Docker container ! ( As such this might be a bit full on for those looking for an introduction to these technologies — there are plenty of good ‘getting started’ articles in Medium for you.)

Why the weather?

This all started off as a response to a coding challenge - to build a simple web service to return the weather (temperature and wind speed) for a location, using WeatherStack as the primary source, and OpenWeatherMap as a failover source. I thought that would be a good start for doing an end-to-end Medium article (my first — so be gentle).

Both services return a JSON object for the current observations at a specified location.

WeatherStack:

(http://api.weatherstack.com/current?access_key=<YOUR ACCESS KEY>&query=Melbourne,AUS&units=m)

{
"request": {
"type": "City",
"query": "Melbourne, Australia",
"language": "en",
"unit": "m"
},
"location": {
"name": "Melbourne",
"country": "Australia",
"region": "Victoria",
"lat": "-37.817",
"lon": "144.967",
"timezone_id": "Australia/Melbourne",
"localtime": "2020-03-04 19:52",
"localtime_epoch": 1583351520,
"utc_offset": "11.0"
},
"current": {
"observation_time": "08:52 AM",
"temperature": 20,
"weather_code": 296,
"weather_icons": [
"https://assets.weatherstack.com/images/wsymbols01_png_64/wsymbol_0017_cloudy_with_light_rain.png"
],
"weather_descriptions": [
"Light Rain"
],
"wind_speed": 11,
"wind_degree": 100,
"wind_dir": "E",
"pressure": 1011,
"precip": 0,
"humidity": 78,
"cloudcover": 75,
"feelslike": 20,
"uv_index": 7,
"visibility": 10,
"is_day": "yes"
}
}

Note: Wind speed is in km/h

OpenWeatherMap:

(http://api.openweathermap.org/data/2.5/weather?q=Melbourne,VIC,AUS&appid=<YOUR API ID>&units=metric)

{
"coord": {
"lon": 144.96,
"lat": -37.81
},
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10n"
}
],
"base": "stations",
"main": {
"temp": 19.45,
"feels_like": 19.01,
"temp_min": 17.22,
"temp_max": 21,
"pressure": 1011,
"humidity": 77
},
"visibility": 10000,
"wind": {
"speed": 3.1,
"deg": 100
},
"clouds": {
"all": 75
},
"dt": 1583313252,
"sys": {
"type": 1,
"id": 9554,
"country": "AU",
"sunrise": 1583266067,
"sunset": 1583312179
},
"timezone": 39600,
"id": 2158177,
"name": "Melbourne",
"cod": 200
}

Note: Wind speed is in m/s

So for a common JSON API we can generate from the 2 different sources I came up with:

{
"location": {
"name": "Melbourne",
"country" : "AU/Australia",
"lat": "-37.817",
"lon": "144.967",
},
"temperature" : 19.45,
"humidity": 77,
"wind" : {
"speed" : 3.1,
"deg" : 100
},
"cloud" : 75,
"source": "OpenWeatherMap",
"pressure" : 1011,
"ts":1583313252
}

Note: Wind speed will be km/h, and the country will be whatever is returned from the respective service. We need that, as it provides validation that we have the right location (ie, “Melbourne, Victoria, Australia” rather than “Melbourne, Florida, USA”).

For reference, this will be built on OSX 10.15.3, using Python 3.7.3, Flask 1.1.1, and ReactJS 16.13.0.

Getting Started…

So to get things rolling lets’ setup the basic project structure and fleshing it out. I will start at the API server (app), but you could start at the frontend and use the sample JSON from above to build out the UI.

Folders:

weather/
└─app/
└─scripts/
└─ui/

So jumping into the app folder, we firstly create our virtual environment to capture the Python libraries (and versions) we will be using, and activate it.

$ python3 -m venv env
$ source ./env/bin/activate
# The command prompt will have (env) prepended when the virtual env # is active
(env) $
# To exit the virtual env ...
(env) $ deactivate
$

We can then install our dependencies …

# Install Flask
(env) $ pip3 install Flask

Then capture the requirements…

# Capture the requirements.txt
(env) $ pip3 freeze > requirements.txt

Now, lets rough out our API server. Create a main.py file, which will hold a quick “Hello world” Flask app…

from flask import Flask, escape, requestapp = Flask(__name__)@app.route('/')
def hello():
name = request.args.get("name", "World")
return f'Hello, {escape(name)}!'
def main():
app.run()
if __name__ == "__main__":
main()

Which we can now run…

$ python3 main.py
* Serving Flask app "main" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

And in your browser, visit “http://localhost:5000/?name=Chris” for a very satisfying “Hello, Chris!” !!

We will now look at containerising the application, so deactivate the virtual environment and ….

Now for some Docker.

So for those familiar with Docker, you will be waiting for the Dockerfile section, so I wont keep you waiting. Here is what it looks like:

FROM python:3.7
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENTRYPOINT ["python3"]
CMD ["main.py"]

We will put that in the app folder for easier management of the context which Docker uses for building the container.

Jumping to the scripts folder now, we will create some convenience shell scripts. These could also be integrated into a build chain, for future CI/CD automation. To start, let’s create a shell script to run the build for us, imaginatively called build.sh ( and don’t forget to chmod a+x build.sh when you are done).

#!/bin/bash#Build the ReactJS app
# coming soon ...
#Build the Docker image
image="whatweather"
docker build -t ${image}:latest ../app

And one to run the docker container for us … run.sh

#!/bin/bash
# -p hostPort:containerPort
docker run -d -p 5000:5000 whatweather:latest
open -a "Google Chrome" http://127.0.0.1:5000/

Running the build script should trigger the Docker build process, which will create a fresh new image for us (called “whatweather:latest”). We can see the images used in the build process, and our new one by running $ docker images .

You should see something like:

REPOSITORY    TAG       IMAGE ID          CREATED             SIZE
whatweather latest 3805f7de1286 8 minutes ago 942MB
python 3.7 f66befd33669 6 days ago 919MB
python 3.7-buster f66befd33669 6 days ago 919MB

Which means we can go ahead and trigger our run.sh to spin up the Docker container for us.

But wait, there is a problem. The browser will open (you do have Chrome installed right ?) but no response. The problem lies in our Flask app is running on the host http://127.0.0.1:5000 or http://localhost:5000 within the Docker container, which are both not publicly viewable ! So even though we expose our containers port 5000 to our computers port 5000, the traffic will not route to our Flask server.

So let’s fix that, by changing the host that Flask binds to, by updating our app/main.py with the following:

# Change
app.run()
# To
app.run(host= '0.0.0.0')

And rebuild our Docker image, and run it again. Note: You may encounter some problems with running containers already being bound to ports, so best to do some docker house cleaning as you go…

# List Docker images:
$ docker images
# Delete image:
$ docker rmi <image id>
# List all containers:
$ docker ps -a
# Remove stopped container:
$ docker rm <container id>
# Kill a container:
$ docker kill <container id>

So all things being equal you should have a running Flask server running in a Docker container, ready to be put to the task of getting some weather data.

In summary…

As this article has grown quite long already, and I need to stop for a coffee, so I’ll split this up into a 2 parter. In this first article we have:

  • Built a basic Flask server
  • Built a Docker image, and deployed it locally.
  • Scripted the build and deploy process.

In Part 2 we will flesh out the server to deliver on the business requirements of providing a highly available weather service, and build a simple UI to interact with it. See you soon !

Image Source: https://unsplash.com/@brunocervera

--

--

Chris Thornhill
Chris Thornhill

Written by Chris Thornhill

Cloud Engineer with a passion for elegant, efficient and performant design.