Getting the weather — a full stack ReactJS/Python Flask API tutorial — Part 3
Putting an app on it…
I’ll try and keep this one short and sweet, just the bare bones, as there are plenty of good articles on writing ReactJS apps — the official React tutorial is a good place to start. ( If you want to skip ahead, the link to the GitHub repo is below — I do that too. )
We will use the create-react-app environment to get things started, so head to the project root folder and enter:
/whatweather$ npx create-react-app ui
Which will do its thing, creating the bare bones environment for us to run, test and build our ReactJS app. Head to the ui
folder now to try it out:
/whatweather/ui$ yarn start
Which will open up a browser pointing at localhost:3000 and you should see:
Nice ! Lets integrate this into our build process …
One container to rule them all ?
Well its by no means best practice, but for demonstration purposes, we will put our react app into our existing container. To deliver the react app, we will need to add either a web server, or modify our app to serve static content. In this case, we will just add an nginx server to our Docker image, and leave the app alone. So our modified Dockerfile will look like:
#Docker image specification for running a Python web service with Flask.FROM python:3.7-busterRUN apt-get update
RUN apt-get install -y nginx
COPY ui/build /var/www/htmlCOPY app /app
WORKDIR /app
RUN pip3 install -r requirements.txt
ENV FLASK_HOST=0.0.0.0
ENV OPENWEATHERMAP_API_KEY=<Your Key Here>
ENV WEATHERSTACK_API_KEY=<Your Key Here>CMD nginx && python3 main.py
The new additions are installing nginx, copying our react app, adding the weather service API keys (missed that in part 2), and changing the container CMD to launch the nginx server, and our Flask server. We will also need to move this to the root folder, so that the app, and ui folders are in the context for Docker to access.
We will also need to update the build script to produce the deployable React app, and update the path to the Dockerfile.
#Build the ReactJS app
cd ../ui
yarn build
cd -#Build the Docker image
image="whatweather"
docker build -t ${image}:latest ..
One little addition thats handy to make to our React package.json
file is to specify the homepage
property equal to .
. This instructs the build process to build the React app with relative paths — meaning we can deploy our app to any folder on the web server and it will be able to load its resources. Not essential, but gives us flexibility in future.
"homepage": ".",
We will build out our application with a main component to render the weather, and another to change the location, as follows:
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── LocationSetter.js
│ └── Weather.js
The Weather
component calls its getWeather
function on mount, and also passes the function to the LocationSetter component, so it can trigger an update when a new city is specified. It also has some rendering helper functions to display icons for the various weather attributes:
Our getWeather
function does the “heavy lifting” of retrieving our data from the weather server — note the URL is hard coded here for a local server.
getWeather(location) {
var weatherURL = `http://127.0.0.1:5000/weather`
if ((location != undefined) && (location != "")) weatherURL = weatherURL + "?location="+ location;
fetch(weatherURL)
.then(res => {
return res.json();
}).then(data => {
console.log(data);
this.setState({ weather: data })
})
}
The LocationSetter
component is simply a one field form and submit button. At the moment, it doesn't perform any validation on the data entered — probably a good idea for a production implementation ! As mentioned, the click event on the button triggers the getWeather
function above with the entered location.
You may recall that our weather data includes the latitude and longitude for the selected city — we can use this to integrate into other web services, as URL parameters.
For example…
# Google Maps
https://www.google.com.au/maps/place/<lat>,<lon>
# Windy (10 is the zoom level)
https://www.windy.com/?<lat>,<lon>,10
To assist with formatting and to get a responsive layout, I have use Bootstrap in this project. To add Bootstrap support, I just added the following lines to the public/index.html
head section:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
Which just loads the JS and CSS resources for Bootstrap support from the CDN site.
One “gotcha” to watch out for if using VS Code for your projects, is when saving a .js
file, it will attempt to reformat you code. To properly manage ReactJS files, select the correct format in the bottom right of you editor window…
Where is the code ?
I know this article is a bit light on the coding walkthrough — the UI is really just a user friendly consumer of the web api we built in the first 2 articles. So isn't anything fancy, and I’m not a UI design guru, so there are a lot of things that could be done better (like accessibility for one). So I’ll leave you to bring your awesome to make a better interface — think of this as just a starting point.
If you want a challenge, you can try and implement the app as shown above from scratch, or you can head over to my GitHub account and download it to jump right in.
Wrapping Up
So I hope this set of articles was useful. It was by no means the only way this could have been done — Im already thinking of a series of followup articles on implementing this system with NodeJS, Java, and maybe even PHP, and on the front end using React Native — but thats for another time.
Wrapping up we have:
- Setup a web api to consume 2 third-party web services.
- Setup a web ui to consume that api
- Built a simple CI/CD pipeline to generate a container bundling our api server and the UI.
Not bad !
For those who have ventured here first, here are the links to earlier articles for Part 1 and Part 2.