Create and Deploy your first Flask app
Tags: python, webdev, beginners, tutorial
Published: 2022-05-17 10:31:43

So, you’ve been banging your head for some time now trying to understand -

<br />

> *what is this Flask framework and how do I use it*
> 

<br />

I know, I’ve been there too. But don’t worry, you don’t have to go through all the pain of searching articles for days to understand it’s functionalities.

<br />

Here, we’ll make a simple weather application using Flask to understand it’s functionalities and also cover some core concepts like -

1. Creating and maintaining virtual environments using ***pipenv***
2. How does **routes** work in Flask
3. How to render HTML **Templates** in Flask
4. Interacting with public APIs
5. Passing data from **Python** to **HTML**
6. How to get and process **user input**
7. How to serve **static files**(CSS, JS, images etc) in Flask
8. How to work with **environment variables** in python
9. **Deploying** your ****Flask app on Heroku

Right, the list seems quite a bit for a simple app but trust me, you’ll zoom past it in no time!

<br />

Here’s what we’re gonna build - [Live app](https://kaushalsharma-flaskweatherapp.herokuapp.com/)

{% embed https://www.loom.com/share/ea615549ba9c43cb9f7812a31b5a7f90 %}

And let’s get right into it!

<br />

### Creating and maintaining virtual environments using *pipenv*

First, we’ll create a new folder for our project, I’ll name it “SimpleFlaskWeatherApp”, and then initialise a virtual environment.

<br />

For creating a virtual environment i’ll be using ***pipenv*** rather than virtualenv or pyenv. ***pipenv*** was first released in 2018 and it aims to simply the workflow by bringing the packaging concepts from bundlers, npm, yarn etc to python!

<br />

It automatically creates and manages the virtualenv for your projects by creating a `Pipfile` and `Pipfile.lock` without you having to do anything except installing the packages you need!

<br />

First install pipenv by entering the command in your terminal-

```bash
pip install pipenv
```

if you have multiple versions of python then type the command,

```bash
python3.x -m pip install pipenv
```

where x would be your choice of python version installed on your system

Now, to create a virtual env, go into your project folder and type -

```bash
pipenv shell
```

<br />

this should create a virtualenv for your project with the name of your project folder followed by some random characters.

In my case, you can see it on the right of the image, it’s “SimpleFlaskWeatherApp-0rR_1i25”

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2ko05ip40o0qf6ezyoy.png)

<br />

If you list the contents of your directory, you would see that `pipenv` has created a `Pipfile` which it would use to manage your entire project dependency. It looks something like this -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tarw2g1gdg8fv56pzkdn.png)

If you now want to install any python package, simple type the following command

```bash
pipenv install flask
```

<br />

This would install and make the package available to your project and also add it to your `Pipfile` automatically!

If you later want to pass this environment to someone else then you don’t have to create a requirements.txt, just give them your `Pipfile.lock` file. Then all they have to do is run the command `pipenv install` and **pipenv** will automatically create a virtualenv and install all the packages that are required and re-create the exact same environment.

How cool is that!

<br />

After installing flask, our Pipfile looks something like this -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pb7xr1vch41oo1qxf5gr.png)

If you want to exit your virtual environment, simply type `exit` and press enter. And to activate it again, type `pipenv shell`

Since we’ve covered pipenv, let’s move on to creating our project!

<br />

For context, this is what our project directory looks like -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4uloekbc9a2vcyx22m03.png)

<br />

### How does routes work in Flask

Go ahead and create a file called **app.py,** we’ll write a starter code for a basic flask application like below -

```python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "<h1>This is working!</h1>"

if __name__ == '__main__':
    app.run()
```

and run the python file

```bash
python app.py
```

you should get the following output -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9v8i1tj0itlc72b9oc9d.png)

<br />

In a flask application, the function following the `app.route` is the logic that should be run if a user open the route specified in the `@app.route` decorator

Since we wrote `@app.route('/')` , where “/” means the index route, if the user opens [localhost:5000](http://localhost:5000), the `index` function logic would be executed and whatever the function returns would be returned to the user.

<br />

### How to render HTML Templates in Flask

We’ve returned the html as a string for now just to test whether our application works or not, but this is not practical if you want to render more complex pages with data that will be returned by us later in this article.

<br />

So, instead of writing the html as a string we will create HTML files and return them instead. 

Go ahead and create a `templates` folder in your project directory and inside this templates directory create an html file called `index.html`

<br />

Our directory structure looks something like this -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkenh7g8v0cosw52km4s.png)

<br />

Inside the index.html, write a simple html code as follows -

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask Weather App</title>
</head>

<body>
    <h1>This template is working</h1>
</body>

</html>
```

<br />

Now, in our app.py, along with `Flask` module also import `render_template` module which will allow us to serve html files to the users.

And in the `index` function, simply type `return render_template('index.html')`

<br />

So our code looks like this now -

```python
from flask import Flask, render_template # new

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html') # new

if __name__ == '__main__':
    app.run()
```

<br />

Now, if you run your application, you should see the output as below -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4y8gq902lny0o9s52po.png)

<br />

The `render_template` function looks for the html template specified inside the templates folder that we created earlier.

<br />

### Interacting with public APIs

Now that we have our flask application set up to render html templates, let’s start working with public APIs.

We’ll be using the Current Weather Data of [openweathermap.org](https://openweathermap.org/api) to get weather data for free

Sign up on the website and subscribe to the Current Weather Data API. 

They should provide you with an **API key** to access that API. You can find it [here](https://home.openweathermap.org/api_keys). We’ll be using this API Key to make API calls to get the current weather data!

First, Let’s do a simple call to the API and make sure that the API is returning data as expected.

<br />

We’ll call the API using the `requests` library in python as follows -

```python
API = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
city = 'Mumbai'
api_key = "<your_openweather_api_key>"
try:
    weather = requests.get(API.format(
        city=city, api_key=api_key)).json()
except Exception as e:
    print(f"Error in fetching data")
```

<br />

It is always a good idea to use `try except` blocks wherever you are not sure about the output

Now this piece of code will make a call to the API using the `city` and `api_key` variables and store the result in `weather` variable

but before we can display this result to the user, we must first **check** that infact, we have got back a **correct result**!

In `HTTP` status codes, **200** means that the API call was **success** and you have received some data and **404** means there was some **error** when you sent the API request.

We’ll be checking the status code of our API call by checking the key value `cod` that would be present in the `weather` variable after the API call was made. if the value of `cod` is 200, that means we have correct data, if it is 404 or if we did not receive an output from the API, then it means we made a mistake while calling the API.

<br />

The code for that would be like this -

```python
if weather["cod"] == 200:
				# We got correct result
        return render_template('index.html')
    else:
			# We got incorrect/no result
	    return render_template('error.html')
```

Create an `error.html` file alongside your `index.html` file in the **templates** folder, and just write a simple code to let you know that some error has occured

```html
<h1>Some error ocurred while making the API call</h1>
```

Ideally, you should have proper error handling for all the status codes like `4xx` and `5xx` , but for the context of this article is should suffice.

<br />

Our code for `app.py` looks something like this now -

```python
import requests
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
    city = 'Mumbai'
    api_key = "<your_openweather_api_key>"
    try:
        weather = requests.get(URL.format(
            city=city, api_key=api_key)).json()
    except Exception as e:
        print(f"Error in fetching data")
    if weather["cod"] == 200:
        return render_template('index.html')
    else:
        return render_template('error.html')

if __name__ == '__main__':
    app.run()
```

<br />

If you restart the app and open **localhost:5000**, you would still get the “**This template is working**” message which we wrote in the html.

<br />

### Passing data from **Python** to **HTML**

You must be wondering

<br />

> Why is the data that we received from the API call not showing in our template?
> 

<br />

That’s because we have to pass that data, or infact any data that we want to show to the user, to the template in the `render_template` function

<br />

Here’s how we would do it -

```python
return render_template('index.html', weather=weather)
```

<br />

And in your `index.html` file, simply write the variable name enclosed within **two curly braces** like below -

```html
...
<body>
    <h1>This template is working</h1>
    <div>{{ weather }}</div>
</body>
...
```

Now if you restart your app and refresh the page, you should be able to see the data that is returned by the API we called!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckvnvha40e8nqufqdgpi.png)

Sweet! now we can pass data from our python code and show it to the user!

<br />

Python frameworks like **Flask** or **Django** use a templating language for html called **Jinja2** which enables us to pass data from our python code and display it to the user and also get data from the html given by user and use it in our python logic, which is how we were able to pass the data from python to html.

<br />

> But we do not want to show the user all of the data. how do we select only certain parts it?
> 

<br />

Since it’s a **dictionary**, we can access it the way we acess a dictionary, but there is a twist to it.

we cannot access the dictionary like `my_dict["key"]` . To do it in html, or rather jinja, we would do `my_dict.key`, we have to use the **dot notation**!

Let’s filter out the raw `weather` data and keep only the useful bits! Our `index.html` after filtering would look like this -

```html
...
<body>
    <h1>This template is working</h1>
    <div>
        <div>{{ weather.timeOfDay }}</div>
        <div>{{ weather.weather.0.main }}</div>
    </div>
    <div>
        <p>{{ weather.name }} <span>{{ weather.sys.country }}</span></p>
    </div>
    <div>
        <p>{{ weather.main.temp }}</p>
        <div>
            <div>
                <p>{{ weather.main.humidity }}</p>
            </div>
            <div>
                <p>{{ weather.wind.speed }}</p>
            </div>
        </div>
    </div>
</body>
...
```

We can also use **control flow** like `if else` or `for loop` in our html file! we’ll wrap all the divs containing reference to weather variable after our `h1` tag under an if statement so that we only show the result from the API call if we have some data in the weather variable, else we do not show anything

```html
<body>
    <h1>This template is working</h1>
    {% if weather %}  <!-- new -->
    <div>
        <div>{{ weather.timeOfDay }}</div>
		. . .
        </div>
    </div>
    {% endif %}  <!-- new -->
</body>
```

<br />

Now if you restart your app and refresh, you should see something like this -

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylidw7by4dulrjadozrc.png)

This is all the data that we have to show the user!

<br />

### How to get and process user input

Let’s now add a **search box** that will allow the user to search for places they like and get details of those places. Add a `form` and inside that form add a `input` search box to get input of city and a `button` to submit the query.

<br />

```html
...
<h1>This template is working</h1>
    <div>
        <form method="post" action="{{ url_for('index') }}">
            <input type="search" name="city">
            <button type="submit">Submit</button>
        </form>
    </div>
    {% if weather %}
	...
```

The form in html needs the action attribute which would typically be a url the html will redirect to once the form is submitted. Here, we provide the url for our `index` page itself using the `url_for` function inside double curly braces. Note inside the `url_for` we give the **function name** and not the route, that’s because in flask the route and function name should be **identical**!

This is also one of **Jinja2**’s features!

<br />

Since now are are submitting a **post** request from our page, we need to **handle** that in our function as well.

Flask let’s us **identify** whether a **request** is `GET` request or a `POST` request simply by doing `request.method` inside the function.

We will now update the index function in our app.py by checking the type of request the function is receiving. 

If the request is a `GET` request then just render the index.html form without any processing or API calls since it’s the first time user is loading the page.

If the request is a `POST` request, then we would **read** the **city** name that was submitted by the user and make the **API call** to get the weather data for that city and **return** the data to our index.html so that we can show the data to the user

<br />

Let’s update the index function to handle different types of requests,

```python
from crypt import methods
import requests
from flask import Flask, render_template, request # new

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST']) # new
def index():
    if request.method == 'POST': # new
        URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
				city = request.form.get('city') # new
        api_key = '<your_openweather_api_key>'
        try:
            weather = requests.get(URL.format(
                city=city, api_key=api_key)).json()
        except Exception as e:
            print(f"Error in fetching data")
        if weather["cod"] == 200:
            return render_template('index.html', weather=weather)
        else:
            return render_template('error.html')
    else:
        return render_template('index.html')

if __name__ == '__main__':
    app.run()
```

Now, if you **restart** the app and **refresh** the page, you’ll get an **empty** page because when the page is first loaded the form is not submitted hence it is a `GET` request. 

<br />

If you now enter a city name and press submit, you should get weather data for that city!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5p8wvvm1l7kpulrq8rw.png)

<br />

But this page looks a bit dull to be honest, let’s add some CSS styling and make it come alive!

<br />

### How to serve static files(CSS, JS, images etc) in Flask

First, create a **static** folder in your project root directory alongside the **templates** folder, and add a `style.css` to it

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gotq3nmyjhrk3uzhbj4g.png)

Add some basic code in your `style.css`

```css
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,300;0,400;0,600;1,400&display=swap");

*,
*::before,
*::after {
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
}

body {
  height: 100vh;
  width: 100vw;
  font-family: "Source Sans Pro", sans-serif;
  margin: 0;
	background: linear-gradient(blueviolet, skyblue);
}
```

And to load this CSS in your html file, add a `link` tag to your html

```html
...
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <!-- new -->
    <title>Flask Weather App</title>
</head>
...
```

<br />

To load the CSS input html, we again use `url_for` but this time we give it **two arguments**.

The **first** is the **name** of our static folder which is **static** and **second** we give it a **filename** which in our case is `style.css`

<br />

You should see something like this,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xel65f6w9vvsuhu8x4h.png)

And now you can customise your page however you like!

Since styling a page is very subjective, i’ll leave it upto you now. However, if you want to style it like how it is in the video at the start, i’ll link my github repo at the end of this article!

<br />

### How to work with environment variables in python

Remember how we are using an API key to make the API calls. Well that **API key** is specific to you and should always be **hidden** from others. You should also never upload this key anywhere, even on github. 

But then you might ask, 

<br />

> How can we use it in out project, **without** having it in our project ?!
> 

<br />

Well, we can make use of something called **environment variables**. We can **add** some variables to the **environment** that is running our code, and **access** those variables in our code whenever we want!

<br />

To accomplish this, first create a `.env` file in our project root directory

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d7o089pc9s87wjhr4pfh.png)

And inside this `.env` file, assign your API key to a variable like so

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v8cs41ya4022spx1wmc6.png)

Make sure you don’t add any **quotes**, just **paste** your key right after the equals to sign

<br />

Now to access this API key in our project, we need to install a library called `python-dotenv`

```bash
pipenv install python-dotenv
```

And we now import and initialize the packages we just installed in our project

```python
import os #new
from crypt import methods
import requests
from flask import Flask, render_template, request
from dotenv import load_dotenv, find_dotenv # new

load_dotenv(find_dotenv()) # new

app = Flask(__name__)

...
```

The `find_dotenv` function will **find** the **.env** file located in our project and the `load_dotenv` function will **load** it into our project so we can use it

<br />

Now, to reference the api key variable in our **.env** file in your project we will make use of the **os** library that we imported,

```python
...
if request.method == 'POST':
        URL = 'http://api.openweathermap.org/data/2.5/weather?q={city}&units=imperial&appid={api_key}'
        city = request.form.get('city')
        api_key = os.getenv("OPENW_API_KEY") # new
        try:
            weather = requests.get(URL.format(
                city=city, api_key=api_key)).json()
        except Exception as e:
            print(f"Error in fetching data")
...
```

Restart your app and you would see your application is working just like before!

You can add any **number** of environment **variables** in the **.env** file and use them anywhere in your project!

By using environment variables, we can **secure** **information** that is **private** to us so that no other person can misuse it

<br />

### Deploying your Flask app on Heroku

This project works fine on my laptop but - 

<br />

> what if i want to show it to the people online? How can I **deploy** my project?
> 

<br />

That’s where **[heroku](https://www.heroku.com/)** comes in

**Heroku** is a **cloud platform** that supports deployment of projects created in many different languages, even python!

So go ahead and create an account. After you login, you would see the screen something like this

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2vupdnodw6i8zitc6bxy.png)

To deploy our flask weather application, we’ll have to create a new app on heroku. So click on new

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7of94xo8w8fp7ta1b0sv.png)

and select **Create new app**

after that, type in your **App name** and click **Create app**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wohfgm8w661jx3yhe7po.png)

After that scroll down to **Deploying using Heroku Git** section

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3v2o4cky2c8fchdspha.png)

and simply follow these steps,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqffdqn2q059ize47lsl.png)

Let’s deploy our app!

<br />

First log in to heroku in your terminal, When you type `heroku login` it will open a browser to log you in.

After you’ve logged in, make sure you are in your project root directory, type `git init` to initialise a git repository

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qst6fs1up5dmy9hv9az7.png)

Now add all the changes and commit to the main branch,

```bash
git add .
git commit -m 'pushing to heroku'
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jaaa56qki5l6ycvdz13.png)

Now, set the remote as shown on the heroku website, and push your changes

```bash
heroku git:remote -a <name_of_you_app>
git push heroku main
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zo5mcb0r8d9e9lurcai.png)

**Note**: since we are using environment variables, **any deployment platform does not support .env files**

You’ll have to **manually add your environment variables on the website in your app settings**

In heroku, go over to the settings tab, you should be able to see **Reveal Config Vars** button, click on it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0rvlarsu7rw2eq1m73ua.png)

It will now allow you to add your environment variables just as you did in your `.env` file!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d50ly99xqgk7kmy3nqps.png)

Now if we open our app typing in the terminal,

```bash
heroku open
```

We see an application error!

<br />

This is because we have only **pushed** our application to the platform, we haven’t **run** it yet!

To run you python or any application on platform like these, you need to have a **Procfile**. ****Inside a **Procfile** you add all the process that you want the platform to execute. 

<br />

Let’s add a Procfile to our root project folder

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fiau5gmilkpgs8o0lsc7.png)

Note that the **Procfile does not have any extension**, this is super important!

And inside the procfile add this line,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owbzf85t0tawg5nlxxbb.png)

<br />

Let’s also install gunicorn in our virtual environment by typing the following command in the terminal,

```bash
pipenv install gunicorn
```

Now follow the process to push the changes to heroku again,

```bash
git add .
git commit -m 'adding procfile'
git push heroku main
```

And now if you open your application,

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oce92ucu1er627rohvp.png)

TA-DA! our application is deployed and running online! 🥳

Now you can share what you’ve built over the course of this article with other people!

I know it was quite a long read but hope you got to learn something new, if not new then maybe a better way of doing things.

**Likes** and **Shares** would be deeply appreciated!

Please do not hesitate to message me, you can **text/follow** me on [LinkedIn](https://www.linkedin.com/in/kaushal-sharma-/) or [Instagram](https://www.instagram.com/kaushal_dev.py/) or [Twitter](https://twitter.com/Kaushal_Shawrma)

Make sure you follow to not miss any future articles! 😉

And as promised, [my github repository](https://github.com/skazi019/FlaskWeatherApp)