Build and deploy Flask app with Gunicorn and Nginx

This tutorial describes how to build Flask based bitcoin price web application by using Gunicorn, Nginx and CoinGecko API to access the data from open online service.

Introduction

Flask is a web application framework and in order to serve the content to the client (web browser), web server is needed. There are multiple web server options like Apache via WSGI module or fully stand-alone WSGI server like uWSGI or Gunicorn.

Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It is strongly recommend using Gunicorn behind a proxy server. Although there are many HTTP proxies available, it is strongly advised that you use Nginx. If you choose another proxy server you need to make sure that it buffers slow clients when you use default Gunicorn workers. Without this buffering Gunicorn will be easily susceptible to denial-of-service attacks.

Nginx is an open source software for web serving, reverse proxying, caching, load balancing, media streaming, and more. It started out as a web server designed for maximum performance and stability. In addition to its HTTP server capabilities, Nginx can also function as a proxy server for email (IMAP, POP3, and SMTP) and a reverse proxy and load balancer for HTTP, TCP, and UDP servers.

Build Bitcoin price web app with Flask

In this tutorial PyCharm Python development IDE is used. Before creating the app verify you have installed python 3. If not depends on your distribution available version

$ sudo apt-get update
$ sudo apt-get install python3.9

To create the app by using PyCharm go to New Project

and select project type Flask

Result application structure looks like

Install the required modules

$ pip3 install flask gunicorn pycoingecko

Update app.py file to handle the requests like

from flask import Flask, render_template
from pycoingecko import CoinGeckoAPI
from datetime import datetime

app = Flask(__name__)
cg = CoinGeckoAPI()


@app.route('/')
def bitcoinhistory():
    # historical_data = cg.get_coin_history_by_id(id='bitcoin', date='10-11-2020', localization='false')
    current_price = cg.get_price(ids='bitcoin', vs_currencies='usd')
    historical_data = cg.get_coin_market_chart_by_id(id='bitcoin', vs_currency='usd', days='1')

    xValues = []
    yValues = []

    for p in historical_data['prices']:
        # dt_object = datetime.utcfromtimestamp(p[0] / 1000).strftime('%Y-%m-%d %H:%M:%S')
        dt_object = datetime.utcfromtimestamp(p[0] / 1000).strftime('%H:%M:%S')
        xValues.append(dt_object)
        yValues.append(p[1])

    return render_template('index.html', price="${:.2f}".format(current_price['bitcoin']['usd']), xValues=xValues,
                           yValues=yValues)


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')

Create template in the templates directory index.html

<!DOCTYPE html>
<html lang="en">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>

<head>
    <meta charset="UTF-8">
    <style type="text/css">
        body {
            background: black;
            color: white;
            max-width: 80%;
            margin-top: 100px !important;
            margin-left: 100px !important;
            margin-right: 100px !important;
            float: none !important;
        }
    </style>
    <title>Title</title>
</head>
<body>

<p style="font-size: 60px; font-weight: bold">{{price}}</p>
<p id="current_date"></p>
<canvas id="myChart" style="width:100%; max-width: 900px"></canvas>

<script>
    new Chart("myChart", {
        type: 'line',
        data: {
            labels: {{ xValues | tojson }},
            datasets: [{
                label: 'BTC/USD',
                fill: false,
                backgroundColor: "rgba(255,80,80,1.0)",
                borderColor: "rgba(255,80,80,1.0)",
                data: {{ yValues | tojson }}
            }]
        },
        options: {
            scales: {
                // xAxes: [{gridLines: { color: "#cccccc" }}],
                yAxes: [{gridLines: {color: "#cccccc"}}],
            }
        }
    });

    // print date
    document.getElementById("current_date").innerHTML = new Date().toLocaleDateString();
</script>

</body>
</html>

Test your application by predefined run configuration by PyCharm

and your result should look like

Add WSGI configuration

In order to create wsgi entry point we will add wsgi.py python file in the project directory (same directory as app.py) with the following content

from app import app

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

To test serving app via Gunicorn WSGI server we are going to create the following run configuration in PyCharm

Create WSGI socket

Communication between Gunicorn and Nginx will be socket based communication. In order to create the socket we will update the run configuration like

Socket file in our case is created in the project directory. Gunicorn is started with 2 workers and socket is binded by providing the socket file path. An other argument umask is provided to the parameter list to set the bit mask for the file mode on files written by Gunicorn.

Nginx coinfiguration

Create Nginx configuration file in the nginx configuration sub-directory sites-available

with the following content

server {
   listen 80;
 
   location / {
       include proxy_params;
       proxy_pass http://unix:/data/DEVELOPMENT/TEST/Python/bitcoinprice/wsgi.sock:/;
   }
}

and than create symbolic link in the sites-enabled directory

$ ln -s  /etc/nginx/sites-available/wsgi /etc/nginx/sites-enabled

With this nginx configuration all the requests received on port :80 are proxy passed to the socket.

Test nginx configuration

Test your app by accessing http://localhost

All done!

References

Flask
Gunicorn - WSGI server
NGINX
How to build a Flask app with WSGI and Nginx
CoinGecko

Leave a Reply

Your email address will not be published.

*
*
*