Write a Dead Simple Web App, Fast, for a Hackathon (Part Two): Debug and Deploy
David
Posted on February 19, 2021
This series focuses on equipping you with the basic background needed to make a web app fast for a hackathon. My intent is to provide the simplest information possible to get you up-and-running with something that works for a beginner project.
This is not a series which will teach you how to build a robust, scalable, enterprise-worthy application. So, for instance, I'll be avoiding topics like writing tests š.
You can find a completed snapshot of the code in this tutorial at https://github.com/david-shortman/hackathon-flask-backend-tutorial/tree/main/part%20two
The Common Hackathon Hangups
Two things bog down hackathon projects: figuring out what's wrong with your app, and figuring out how to share it with other people.
In this second part of the DSWAFH (Dead Simple Web App Fast for a Hackathon) series, you'll learn how to use dead simple linting and debug tools for your Flask app, and how to containerize and deploy it to the World Wide Web using Heroku.
I highly and deeply recommend starting at Part One of this series before continuing. Otherwise, you can start this tutorial by cloning and copying the part one directory from the tutorial's repository.
Prevent and Find Mistakes
The editor VSCode makes it unbelievably easy to start debugging our app. Grab a copy of VSCode if you don't have it installed already, and open your app's directory with it.
Set the Python Interpreter
Since we're using VSCode, let's take full advantage of it by making it produce code insights for us. We can get autocomplete, view docs by mouse hovering, and more, by telling VSCode which Python interpreter we're using from our virtual environment.
First, install the Python extension for VSCode- https://marketplace.visualstudio.com/items?itemName=ms-python.python.
Then open VSCode's Command Palette with View > Command Palette
, and search for "Python: Select Interpreter".
Then choose Enter interpreter pathā¦
, then Findā¦
, and use your file explorer to navigate to your project folder's interpreter (like {project_folder}/venv/bin/python
).
You should get a cool prompt in the bottom right corner of VSCode after selecting the interpreter:
VSCode's prompt to install pylint
Go ahead and click "Install" to install pylint in the virtual environment.
Now you'll notice a bunch of great features enabled, like
- Documentation on hover
An IntelliSense popup appearing when hovering over the keyword "flask"
- Autocomplete
An autocomplete menu appearing after typing a dot after a symbol
- And much more
A pylint unused variable warning
With all this help from the editor, you should be well equipped to avoid simple coding mistakes that would otherwise waste your time at a hackathonĀ .
Set a Breakpoint
Those coding insights from VSCode are going to go a long way to helping you avoid syntax mistakes and library mis-usage. But face it, you're gonna still manage to write bad code because you don't have time to write test cases.
Instead, we need a robust debugging system to run code and see what's broken about it. Luckily, VSCode provides one of the fastest to set up debugging tools ever for Flask apps.
On the left side of the editor, click the "Run" icon (made of a play icon and bug icon), and then click "create a launch.json file".
Then select "Flask" in the "Select a debug configuration" popup that appears.
Set the name of the file to "run.py" when the next prompt appears.
A launch.json file will appear that you can completely ignore! Instead, go click the play button in the Run menu on the left.
The run button appears for the Flask run configuration
You should see the same kind of output appear in the VSCode terminal that appeared when we ran our app manually before. The color of the bottom bar of VSCode should have also turned orange to let you know it's actively running something!
Now you can place a breakpoint by clicking to the left of a line number of run.py. Then when you trigger the code for that breakpoint, you'll notice something magical occur.
Try placing a breakpoint on line 16, and then looking up the weather for New York by visiting http://localhost:5000/weather/us/new%20york.
KA-BOOM! VSCode should stop execution of the weather function right at line 16:
VSCode stopped at the breakpoint on line 16
And even more magically, we can hover over variables to see what they're currently set to. And we can even use the Watch pane on the left to type in variables we want to track, and even see where we are in execution in the Call Stack pane.
The debugger shows us that "country" is set to "us". On the left, we can see the values of "country" and "city" in the Watch pane.
At the top of the window you should see a toolbar that has appeared that controls the flow of the application from a breakpoint. You can continue execution, step to the next line, step into a function, step out of the current function, restart the app, or stop it.
This debugging toolbar in VSCode holds a lot of powerful actions
Click the continue button to finish executing the code so you can get weather information for New York in your browser.
Try playing around with the debugging tools to get comfortable with them. They'll be indispensable to figuring out what you wrote wrong on your own projects.
Package and Ship theĀ App
Getting an app on the web can be a tedious and difficult process. So let's use something simple, fast, and fun instead.
Package an App with Docker
Remember that image from Part One that showed that really simplistic view of our app?
A client, like a browser on a smartphone (left), makes a request to a "backend" service (right) using the HTTP protocol.
So far, our local computer has been acting as both the left and right side of the image, both the client and the server.
We want to move the right side, the server, to "the cloud."
But, remember all that setup we had to do on our local machine to make the app run? We had to install a bunch of packages, create a virtual environment, install the dependencies, and use a command to make it run. And, if you noticed in the console output when running the app, we've been getting output at the start that looks like this:
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
So we have two problems- we have to set up a server manually, and figure out a way to run it with a "real" server for deployment.
If there were some way to encapsulate those steps, and create some kind of artifact from it, then we could push that thing to any computer in the world and be able to run our appā¦
It turns out there's a widely popular solution to this problem- containerization!
In short, it allows us to perform the work of setting up a server in a box called a "container," and then run that container on a computer that has the Docker runtime installed.
Create a "real" server
Before I tell you how to use Docker to magically package up the app, let's quickly make some changes that will allow us to use the software "gunicorn" to run our app.
Under line 1 of run.py, add a new import statement:
import os
Then replace
app.run(debug=True)
with the following:
if __name__ == "__main__":
if os.environ.get('IS_CONTAINER') != 'true':
app.run(debug=True)
else:
port = os.environ.get('PORT')
if port == None:
port = '5000'
app.run(host=f'0.0.0.0:{port}')
Worry about what this does later š.
One more quick thing: instead of storing the API key for the weather API in our code, let's change it to read it from the system's environment instead. Change line 12 of run.py to:
owm = OWM(os.environ.get('OPEN_WEATHER_API_KEY'))
Before we run our app now, we'll need to specify the API key in our environment. You can do that a few ways, but for the purpose of being able to start and stop the app for debugging, you should call the following command:
export OPEN_WEATHER_API_KEY=YOUR_API_KEY_HERE
Now, create a new file at the root of the project directory called "server.py" with the following contents:
from run import app
if __name__ == "__main__":
app.run()
Now our app can run both the old way, just using "python run.py", but also using gunicorn.
Let's try using gunicorn to see how to run the app with a "production-grade" server.
We can specify the pip dependencies for our app in a requirements.txt file that pip knows how to read. Create the following requirements.txt that includes gunicorn:
flask==1.1.2
pyowm==3.0.0
gunicorn
Then install the requirements:
pip install -r requirements.txt
Then start the app like so:
gunicorn --bind 0.0.0.0:5000 server:app
This tells gunicorn to run server.py, and run the Flask app we created on line 3 of run.py.
You can visit a weather url in your browser like before and see that the server is working.
Creating anĀ Image
Go ahead and install Docker Desktop. Then come back here and continue.
Now that we have the capability to run a production server, we need to package up all these steps in a thing called an "image" using Docker. This definition of how to build our server will live in a file called a "Dockerfile."
The Dockerfile contains a list of instructions telling Docker how to build an "image" for us. An image is an artifact that is used to create a container for our app. You can reuse an image to create multiple clones (i.e., multiple containers) that could be deployed on many machines (so that our app could be on servers close to many regions of the world, for example).
Let's create a Dockerfile (simply name the file "Dockerfile" with no extension) at the base of our project folder with the following first line:
FROM python:alpine3.7
This first instruction tells Docker we want to create our image from another image that already exists, python:alpine3.7. There's thousands of images out on Docker's registry, but this one happens to already have Python installed on it, as well as some minimum dependencies for us to work with.
Next, let's write the instructions for Docker to install our app's requirements:
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
We create an "app" directory, copy our requirements.txt file to it, and install the requirements.
Then, let's copy our app's files to the image:
COPY run.py server.py ./
Finally, let's expose an argument to set our API key, and write the command we want to execute to run the app:
ARG OPEN_WEATHER_API_KEY
EXPOSE 5000
CMD gunicorn --bind 0.0.0.0:$PORT server:app
Build the Image and Create a Container Locally
Let's make sure our image works by building it and running locally before we try deploying it somewhere.
We can create a definition for how to run a container from the image with a "docker-compose" file. Create "docker-compose.yml" in the root of the project directory with the following:
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
env_file: .env
This tells Docker that we want to build the image using the Dockerfile at the root of the directory, and that we want to expose port 5000 from the container to our system's port 5000.
It also specifies that it should use the environment variables from aĀ .env file. Let's create ".env" at the root of our project's directory with the following:
PORT=5000
OPEN_WEATHER_API_KEY=YOUR_API_KEY_HERE
Now the magic can finally happen. With Docker Desktop installed and running, try out the following command:
docker-compose up
With that simple command, you should see Docker go straight to work building the image, and then starting up a container.
When stuff stops being output to the terminal, try visiting a weather url again, and it should be successful!
Deploy toĀ Heroku
Wow, you've made it so far in this article. We're almost at the finish line. Everything's culminated to this moment.
Time to deploy an app to the web.
Since we have our app ready to be deployed anywhere where Docker is supported, we have huge flexibility in options. But the most popular solutions from Google, Amazon, and Microsoft tend to involve a lot more setup and usually the attachment of billing/credit card information. Gross.
Instead, we can use my personal favorite service for hackathons, Heroku, to deploy our backend app.
Sign up for anĀ account
Go to heroku.com and sign up for an account. I'm sure you'll figure it out š.
Create anĀ app
From Heroku's main dashboard, use the dropdown on the right to create a new app.
Selecting "Create new app" from the Heroku dashboard
Enter any name you want for the app, then click "Create app."
Define Heroku Build
With our app created, Heroku makes it very simple to deploy.
First, we need to add a heroku.yml file in the root of our app's directory:
build:
docker:
web: 'Dockerfile'
The config tells Heroku that it should be creating and deploying a Docker container based on our Dockerfile.
Now, let's take a side track to push our code to GitHub.
Publish code toĀ GitHub
If you don't already have a GitHub account, create one at github.com.
Then, from GitHub's homepage, create a new repository using the "+" icon in the top right, and selecting "New repository."
The "New respository" button on GitHub's frontĀ page
On the next page, name the repository whatever you want, and click "Create repository."
GitHub will then show you some command line instructions for initializing your repo. In the directory where you app is located, run the following modification:
echo "venv\n.env\n.vscode\n__pycache__" > .gitignore
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin YOUR_GITHUB_REPOSITORY_URL
git push -u origin main
Tell Heroku to Behave
Now, you'd think Heroku would respect that heroku.yml
file we just defined, right?
Nope! We have to be very stern and use their CLI to set our build as a "container" one.
Install and log into the Heroku CLI:
brew tap heroku/brew && brew install heroku
heroku login
Then set the stack of your app to "container":
heroku git:remote -a YOUR_APP_NAME
heroku stack:set container
Set the OpenWeather API key
Back in Heroku, we need to set up the environment like we did for our local machine in the .env file.
Click the Settings for the project, then click Reveal Config Vars.
The config vars section for a Heroku app
Set the key of a config var to "OPEN_WEATHER_API_KEY", and the value to your API key. Then click "Hide Config Vars" to complete setting the key.
Connect GitHub and Deploy
I hope you're excited, because this is where it all comes together!
Our final step is to connect our repository to our Heroku app. On your Heroku app dashboard, click the Deploy tab, then in the "Deployment method" section, click GitHub and follow the prompts to authorize GitHub for Heroku.
GitHub deployment method button
In the "Connect to GitHub" section, search for your repository and click "Connect".
If you want, you can use the "Enable Automatic Deploys" button that appears to enable every commit to your main branch to kick off a build.
For now, let's manually deploy a build by clicking "Deploy Branch"!
The status of your deployment will immediately appear in the Manual deploy section.
Heroku shows the status of your build
When it's complete, you'll be conveniently given a "View" button to see your live API! Click it and you should see a familiar greeting...
A wild View button appears. Finally!
Conclusion
At a breakneck pace, you've learned the bare essentials to make edits to a web app with guardrails and debugging capabilities from VSCode, and deploy using Docker and Heroku.
There's a bunch more to learn by playing around yourself in the various tools presented here. If you're having trouble getting set up or understanding something in this article, feel free to discuss below and I'll make updates as needed.
Good luck and happy hacking!
Posted on February 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 19, 2021