Write a Dead Simple Web App, Fast, for a Hackathon (Part One): A Flask Backend
Tags: hacktoberfest, python, webdev
Published: 2020-08-31 04:25:53

*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%20one*

## Let's hack together a web app

If you’ve ever attended a hackathon before, you’ll find the most common category of project is a web app. They’re common because they’re easy to create and share, and there’s a million options available to build and deploy them.

But finding a technology stack for making a web app as a beginner in the hackathon scene can be intimidating, and it’s hard to know what will work well together, or if the thing you’ve discovered is too niche to get help with later.

That’s why I’m starting this series on writing a dead simple web app for a hackathon. I’ve picked well-established, beginner-friendly technologies that are free to use and not tied to any specific big-tech products (sorry Firebase, AWS).

In the first part of this series, I’ll show you some fundamental concepts for creating the core of a web app using Flask, an incredibly popular and simple framework for writing web APIs.

## What we’re making

A web app needs a service with which to communicate. We’re going to make that service, which industry experts might call the “backend” of our app.

![A client, like a browser on a smartphone (left), makes a request to a “backend” service (right) using the HTTP protocol.](https://cdn-images-1.medium.com/max/2000/1*Ezh9mg-b1KrlBtQO9HAkUA.png)*A client, like a browser on a smartphone (left), makes a request to a “backend” service (right) using the HTTP protocol.*

As mentioned, we’re going to use a crazy simple framework to make our backend, called [Flask](https://flask.palletsprojects.com/en/1.1.x/), which features very little code to set up a functioning web server.

By the end of this article, you’ll be able to query the weather in a given city, from a browser, using a backend service you write.

## Setting up your environment

Nothing is worse than showing up to a hackathon and spending time trying to make your computer work. Preparing your environment beforehand is critical. Here’s a list of software we’ll be using for this tutorial, and which will be handy for hackathons in your future.

(This guide expects you to be using macOS, the Linux Subsystem for Windows 10, or a Unix-like system for any of the bash commands referenced)

### Brew 🍺

Brew is a package manager that can install other programs for us, and manage updating them.

Although you should never normally pipe a shell script from an arbitrary website into bash, that’s [just what Homebrew asks us to do on its homepage](https://brew.sh). So let’s live dangerously and run the following in our Terminal application, which will install Brew:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

After a bunch of meaningless output from the terminal, you can confirm Brew installed successfully by running:

    brew --version

### Pyenv

Our Flask backend service is going to be written with Python. Let’s install pyenv which manages Python effectively:

    brew install pyenv

You can verify Pyenv is installed using:

    pyenv --version

Now we should configure Pyenv to manage Python for us on our system (replace bash_profile with zshrc if you use that instead of bash):

    echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile

Then, restart the Terminal instance, or use the following command:

    source ~/.bash_profile

### Python 🐍

Now we can install Python:

    pyenv install 3.8.5

Then, we make our terminal switch to use the version of Python we installed globally:

    pyenv global 3.8.5

You can verify Python is installed using:

    which python

You should see a path to Python under the .pyenv directory.

### virtualenv

Finally, let’s install the utility virtualenv so that we can create a virtual environment in which we install our Python dependencies:

    pip install virtualenv

You can verify virtualenv is installed using:

    virtualenv --version

You’re done installing global software, hooray 🥳!

### Setting Up Project

Create a new folder titled “weather-backend”.

    mkdir weather-backend && cd weather-backend

You can see the path to the folder you created with:

    pwd

### Python Virtual Environment

Our project will depend on other libraries pulled from the internet. The best way to manage dependencies for a Python project is to install them local to a project folder, as opposed to globally.

Create a virtual environment:

    virtualenv venv

Then activate the environment:

    source venv/bin/activate

You’ll notice now that in your terminal, the start of each line begins with “(venv)”. This lets you know you’re in the virtual environment.

If you ever want to leave the environment, simply use the command “deactivate”. Otherwise, whenever you leave and later return to run the Flask application, use the “activate” command above.

### Flask

Let’s install the Flask framework in our virutal environment so we can get started on our app:

    pip install flask==2.0.3

## Getting our first weather report 🌦

Okay, we’ve installed a bunch of software, but now it’s time to write our own.

### How to talk to servers

With Flask, we can write a server that will return data to us when we visit a certain URL in a browser. Go ahead and try visiting the following URL in your browser:

    localhost:5000

You should see some error like the following:

![A server connection error in Safari](https://cdn-images-1.medium.com/max/2000/1*dGeixNtZgdNfja1WWL7yMw.png)*A server connection error in Safari*

Since we aren’t running a service at that address yet, our browser tells us that it can’t connect to “localhost.”

“localhost” is the friendly-name of our computer’s local server, and “5000” is an arbitrary port number that our Flask app will happen to run on shortly.

### Make it say “hello” 👋

Alright, let’s make our server start talking. First, create a file called “run.py”. This will be the Python file we run to start our app. Write the following:

    #!/venv/bin/python
    from flask import Flask
    app = Flask(__name__)

    # Keep this at the bottom of run.py
    app.run(debug=True)

All this file is doing so far is importing Flask and instantiating a new app instance. **Make sure to keep the last line at the bottom of the file as we add new code.**

Now let’s add some code to make our server greet us:

    @app.route('/')
    def hello():
        return 'Hello there' 

Does it work? Let’s run it to find out:

    python run.py

You should see some output in the terminal that looks like this:

    * Serving Flask app "app" (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: on
    * Running on [http://127.0.0.1:5000/](http://127.0.0.1:5000/) (Press CTRL+C to quit)
    * Restarting with stat
    * Debugger is active!
    * Debugger PIN: 228-732-681

Now, we can reload [localhost:5000](http://localhost:5000) in our browser.

![The server finally talks back to us by saying “Hello there”](https://cdn-images-1.medium.com/max/2000/1*j1czB7jDo5G5PeOFRRTM1g.png)*The server finally talks back to us by saying “Hello there”*

Okay wow, we’re on speaking terms with our app now. Of course it’s not much of a conversation… but it’s something!

So, how does that “hello” function work, anyway?

Well, since we’re writing a Flask app, we defined code that runs at a certain “route” for [localhost:5000](http://localhost:5000). We configured the “hello” function to run for the default route “/” using the app.route annotation.

    @app.route('/')
    def hello():
        return 'Hello there'

### Another route 🛣

Let’s define another route for our weather report. Add the following to “run.py”:

    @app.route('/weather')
    def weather():
        return 'It\'s always sunny in Philadelphia'

Then visit [localhost:5000/weather](http://localhost:5000/weather) in the browser and you should see the following:

![Apparently, the server thinks it is always sunny in Philadelphia](https://cdn-images-1.medium.com/max/2000/1*BjIeQZA74COK4nauVvmq9w.png)*Apparently, the server thinks it is always sunny in Philadelphia*

See how the routing works now? We define routes using the app.route annotation in app.py, and then when we visit the URL matching that route, we get the return value of the annotated function.

### Conversational routing 🗣

If we want the weather for a given city, we’re going to need a way to specify a city to our server.

But how our we going to do that without a user interface?

…Stumped? How about we use routes!

Now, we could make a route for every city we want to be able to check the weather for. That would be a pretty “hackathon”-esque move, but there’s an easy trick with Flask to be even lazier.

Try modifying the weather function to be the following:

    @app.route('/weather/<city>')
    def weather(city):
        return f'It\'s always sunny in {city}' 

Then try visiting [localhost:5000/weather/boston](http://localhost:5000/weather/boston).

You should see that the city name we provided in the URL is repeated back to us!

![Now the server tells us it is always sunny everywhere](https://cdn-images-1.medium.com/max/2000/1*o_2y5v6SZSse1_PTKMvECw.png)*Now the server tells us it is always sunny everywhere*

### Getting the real weather

Now that we have a mechanism to give the server a city’s name (and there are more mechanisms we’ll get to in the future), we can try providing accurate current weather conditions.

OpenWeather has a free to use membership for their API which we can query really easily with the “pyowm” Python package.

First, sign up for an account on [https://home.openweathermap.org](https://home.openweathermap.org). Then navigate to the “API keys” tab and see that you have an API key which will grant you access to their weather data.

![OpenWeather API keys page](https://cdn-images-1.medium.com/max/2642/1*2ZCS1fEyukLn4HJUZ7BYqg.png)*OpenWeather API keys page*

Back in the terminal, let’s install the pyowm package into our virtual environment:

    pip install pyowm==3.0.0

Now we can use the pyowm package to grab some weather data (and you can learn more about the package on [their README](https://github.com/csparpa/pyowm#get-started)):

    from pyowm import OWM

    owm = OWM('YOUR API KEY HERE')

    @app.route('/weather/<city>')
    def weather(city):
        weather_manager = owm.weather_manager()
        weather_at_place = weather_manager.weather_at_place(f'{city},US')
        return weather_at_place.weather.temperature('celsius')

If we re-visit [http://localhost:5000/weather/boston](http://localhost:5000/weather/boston), you should see something like the following:

![The /boston endpoint returns some weather data](https://cdn-images-1.medium.com/max/2000/1*zEk7lwqaPATK9wmJm0c4uw.png)*The /boston endpoint returns some weather data*

Whoa! Weather ⛈! Who would have thought?!

You’ll notice that Flask did some work for us by converting the Python object we returned at the end of the weather function and turned it into a string that our browser could interpret and display. That feature is one of the things that makes Flask so easy to use right out of the box! We can return complex objects from our server in the format you see above, called JSON, which is really easy for other apps (like a UI) to interpret.

Just to prove the point, let’s just return the current temperature, but both in Celsius and in Kelvin:

    @app.route('/weather/<city>')
    def weather(city):
        weather_manager = owm.weather_manager()
        weather_at_place = weather_manager.weather_at_place(f'{city},US')
        temperature = weather_at_place.weather.temperature('celsius')
        return {
            'temp_celsius': temperature['temp'],
            'temp_kelvin': temperature['temp'] + 273
        }

And now refreshing [http://localhost:5000/weather/boston](http://localhost:5000/weather/boston), we’ll see our new object:

![We get back our new object containing the temperature in Celsius and Kelvin](https://cdn-images-1.medium.com/max/2000/1*cq73b1tSKuxasTSXGdXKWg.png)*We get back our new object containing the temperature in Celsius and Kelvin*

### More parameters, please 🙏

Our weather app is so good now, it seems tempting to go ahead and submit it to the next hackathon you attend, right?

But our weather API is pretty limited right now. All we can do is ask for the temperature in cities in the US. What about other countries? And what about other weather conditions?

Not to fear, more parameters are here. Let’s tackle the country problem first by extending our weather function to take in a second parameter, a country code:

    @app.route('/weather/<country>/<city>')
    def weather(country, city):
        weather_manager = owm.weather_manager()
        weather_at_place = weather_manager.weather_at_place(f'{country},{city}')
        temperature = weather_at_place.weather.temperature('celsius')
        return {
            'temp_celsius': temperature['temp'],
            'temp_kelvin': temperature['temp'] + 273
        }

Now let’s try checking the weather over the pond at [http://localhost:5000/weather/uk/london](http://localhost:5000/weather/uk/london). You should see some new temperatures come back!

Knowing the temperature is good and all, but how can we find out more? We could change our weather function to return a bunch of weather data, but that might be annoying to scroll through.

Instead, let’s modify our endpoint to take in *query params* to specify what features of the weather we’d like to retrieve.

Query params are specified in a URL by using the “?” character followed by a series of paramater/value pairs separated by “&”.

For example, if we wanted to know the humidy and current precipitation, we could specify

    ?humidity=1&detail=1

at the end of our URL.

We can import the “requests” module from Flask to load details from the request, like the query params:

    from flask import request

Then we can modify the weather function one last time to include the humidity and last hour of precipitation when specified:

    @app.route('/weather/<country>/<city>')
    def weather(country, city):
        weather_manager = owm.weather_manager()
        weather_at_place = weather_manager.weather_at_place(f'{country},{city}')
        temperature = weather_at_place.weather.temperature('celsius')
        
        weather_details = {
            'temp_celsius': temperature['temp'],
            'temp_kelvin': temperature['temp'] + 273
        }

        if (request.args.get('humidity')):
            weather_details['humidity'] = weather_at_place.weather.humidity

        if (request.args.get('detail')):
            weather_details['detail'] = weather_at_place.weather.detailed_status

        return weather_details

Try visiting http://localhost:5000/weather/us/maine?humidity=1&detail=1 and see what appears 👀.

## Wrap-Up

By building this weather API backend you’ve learned

* How to run a Flask app

* How to make requests to a server in a browser

* How to specify route parameters and query parameters in a URL

* How to use a third party Python package

* How to read route parameters and query parameters with Flask

That’s a really solid start!

Continue to Part 2: https://dev.to/davidshortman/write-a-dead-simple-web-app-fast-for-a-hackathon-part-two-debug-and-deploy-927