DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Flask Web Application for Smart Honeypot Deployment Using Reinforcement Learning
  • The Cutting Edge of Web Application Development: What To Expect in 2024
  • Building a Tool To Generate Text With OpenAI’s GPT-4 Model
  • Ensuring Security and Compliance: A Detailed Guide to Testing the OAuth 2.0 Authorization Flow in Python Web Applications

Trending

  • MuleSoft MCP and A2A in Production: What 17 Recipes Reveal
  • Using LLMs to Automate Data Cleaning and Transformation Pipelines
  • Multi-Scale Feature Learning in CNN and U-Net Architectures
  • The Serverless Illusion: When “Pay for What You Use” Becomes Expensive
  1. DZone
  2. Coding
  3. Frameworks
  4. Whatz the Good Word: PWA With Flask

Whatz the Good Word: PWA With Flask

This article shows how to build a simple vocabulary building game written as a progressive web application running on Flask.

By 
Mahboob Hussain user avatar
Mahboob Hussain
·
Dec. 29, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.6K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

Though I had not planned it initially, I purchased a domain name for my cloud server on which run the two Rails applications described in my previous DZone articles.

Some months ago, I had written a word game as a Flask web application. The code was inside my laptop, and I decided to put it out in the public domain. However, to give it a mobile app feel, I decided to make it a Progressive Web Application (PWA).

The first requirement for a PWA is that it should be served over HTTPS. My cloud server runs on Ubuntu which has snap pre-loaded, so the easiest way to switch to HTTPS is to run snap to install the utility certbot and run certbot for installing Lets Encrypt certificates and configuring Nginx. But Lets Encrypt certificates can be installed for domain names only, not for IP addresses. I did a quick search for domain names, and the cheapest one available was mahboob.xyz, which was perfect for hosting my side projects, so I bought it.

The commands I ran as root for enabling HTTPS are as follows:

Shell
 




x


 
1
$ snap install core; snap refresh core
2
$ apt remove certbot
3
$ snap install --classic certbot
4
$ ln -s /snap/bin/certbot /usr/bin/certbot
5
$ certbot --nginx
6
$ certbot renew --dry-run



For the IP address to domain name mapping, all I had to do was go into my domain registrar account and give the named servers of my hosting provider. In the hosting account control panel, I went to the Domains section and in just very few clicks the mapping was done. 

PWA

As John Price explained in his article, How to Turn Your Website into a PWA, the advantages of a PWA are:

  • Offline capable
  • Installable
  • Improved performance
  • App-like experience
  • Push notifications
  • Discoverable
  • Linkable
  • Always up to date
  • Safe
  • Cost-effective

The real kicker is the last point. You don't have to invest time and money to write any mobile application code, whether native or cross-platform. Whatever you have used for your responsive web application is good to go; with minimal changes, it gets the features and feel of a mobile application.

My application is a simple word game. The user gets a clue and they have to guess the word. On a button click, they will be told whether they got the answer right or not. If the user wants to know the answer they will be shown the answer. If the user wants a different word, they will get a new clue for it.

The clues and answers are stored in a pipe-delimited flat file on the server. A couple of lines from the file are shown in the following screenshot.

Screenshot of a couple of lines from the file.

When the Flask application starts, it reads the file contents into a data array. The root action is an index, which reads a random element from the array, splits it on the pipe character, and sends the first part (the clue) and the element's index to the game page. The clue is displayed as text and the index is kept as a hidden variable.

Clue display.


Buttons Functionality

  • Check: It invokes the action method check, sending the index and the answer the user entered in the text field. The action retrieves the element from the data array using the index, splits it into pipe character and checks the second part (correct answer) with the user's answer. If they are the same, the method returns "You got it right!", else it returns "Wrong Answer! Please try Again!!". If the answer is correct, this button itself and the "Show Answer" button are hidden.
  • Show Answer: This button invokes the action show, sending the index. The action method retrieves the element from the data array using the index, splits it into pipe character, and sends the correct answer back to the game page. After receiving the response from the server, the game page hides the input text field, Check and Show Answer buttons.
  • New Word Clue: The functionality for this button is the same as the index. It invokes the action "new", which reads a random element from the array, splits it on the pipe character, and sends the first part (the clue) and the element's index to the page. The answer text field is cleared and the Check and Show Answer buttons are explicitly unhidden by calling the show method.

All the buttons make AJAX GET calls via JQuery.

PWA Requirements

In order to convert a web application to a PWA, there are three main requirements.

  1. Run it over HTTPS.
  2. Create and serve a manifest file in JSON format.
  3. Create and serve a JavaScript file to be registered as a service worker file.

My service worker JavaScript file is called serviceworker.js. The game page registers it with the following code:

JavaScript
 




xxxxxxxxxx
1
11


 
1
if ('serviceWorker' in navigator) {
2
    window.addEventListener('load', function() {
3
        navigator.serviceWorker
4
        .register('/wtgw/static/serviceworker.js', {scope: '/wtgw/'})
5
        .then(function(registration) {
6
            console.log('ServiceWorker registration successful with scope: ', registration.scope);
7
        }, function(err) {
8
            console.log('ServiceWorker registration failed: ', err);
9
        });
10
    });
11
}



I used the online Web App Manifest Generator to create manifest.json. The manifest.json and serviceworker.js files are placed in the static folder, from which Flask serves public assets without requiring server-side routing. 

The serviceworker.js file has event listeners for installing itself, opening the cache, activating the cache, and adding/fetching URLs and responses to/from the cache. It also handles two custom features in the fetch event handler.

  1. Getting a new word clue should not be cached. If it's cached, the same clue will be shown again and again from the cache. Preventing this is achieved by a check for the URL "https://mahboob.xyz/wtgw/new" and if yes, the code returns from the function, thus giving a pass-through to the server without checking the cache.
  2. If the user event has not been cached and the user is not connected to the internet, then the call to the cache returns with the response "You seem to be offline, please try after you're online."

The fetch event handler code is shown below: /static/serviceworker.js

JavaScript
 




xxxxxxxxxx
1
36


 
1
self.addEventListener('fetch', function(event) {
2
    if (event.request.url === "https://mahboob.xyz/wtgw/new") {
3
        return;
4
    }
5

          
6
    event.respondWith(
7
        caches.match(event.request)
8
        .then(function(response) {
9
            if (response) {
10
                return response;
11
            }
12
            return fetch(event.request)
13
            .then (
14
                function(response) {
15
                    if (!response || response.status !== 200 || response.type !== 'basic') {
16
                        return response;
17
                    }
18
                    var responseToCache = response.clone();
19
                    caches.open(CACHE_NAME)
20
                    .then(function(cache) {
21
                        cache.put(event.request, responseToCache);
22
                    });
23
                    return response;
24
                }
25
            )
26
            .catch(
27
                function(err) {
28
                    return caches.open(CACHE_NAME)
29
                    .then(function(cache) {
30
                        return new Response("You seem to be offline, please try after you're online");
31
                    });
32
                }
33
            );
34
        })
35
    );
36
});



Deployment

On mahboob.xyz, the application is run by gunicorn, which requires the file wsgi.py, and is set up as a service. It is co-located along with two Rails applications. The application service and Nginx configuration are given below: /etc/systemd/system/wtgw.service 

Properties files
 




xxxxxxxxxx
1
11


 
1
[Unit]
2
Description=Gunicorn instance for MH Whatz The Good Word
3
After=network.target
4

          
5
[Service]
6
User=root
7
WorkingDirectory=/var/www/mh-wtgw
8
ExecStart=/usr/local/bin/gunicorn --workers 3 --bind unix:mh-wtgw.sock wsgi:app
9

          
10
[Install]
11
WantedBy=multi-user.target



/etc/nginx/sites-enabled/mh_sites 

Nginx
 




xxxxxxxxxx
1
59


 
1
server {
2
    server_name mahboob.xyz;
3
    passenger_enabled on;
4
    passenger_ruby /usr/bin/ruby;
5
    passenger_app_env development;
6

          
7
    location / {
8
        root /var/www/html;
9
    }
10

          
11
    location ~ ^/rbf(/.*|$) {
12
        alias /var/www/rails6-bootstrap-flatpickr/public$1;
13
        passenger_base_uri /rbf;
14
        passenger_app_root /var/www/rails6-bootstrap-flatpickr;
15
        passenger_document_root /var/www/rails6-bootstrap-flatpickr/public;
16
    }
17

          
18
    location ~ ^/cdb(/.*|$) {
19
        alias /var/www/dashboard/public$1;
20
        passenger_base_uri /cdb;
21
        passenger_app_root /var/www/dashboard;
22
        passenger_document_root /var/www/dashboard/public;
23
    }
24

          
25
    location /wtgw/ {
26
        include proxy_params;
27
        add_header Service-Worker-Allowed /;
28
        proxy_pass http://unix:/var/www/mh-wtgw/mh-wtgw.sock:/;
29
    }
30
        
31
    error_page   500 502 503 504  /50x.html;
32
    location = /50x.html {
33
        root   html;
34
    }
35

          
36
    listen 443 ssl; # managed by Certbot
37
    ssl_certificate /etc/letsencrypt/live/mahboob.xyz/fullchain.pem; # managed by Certbot
38
    ssl_certificate_key /etc/letsencrypt/live/mahboob.xyz/privkey.pem; # managed by Certbot
39
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
40
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
41
}
42

          
43
server {
44
    if ($host = mahboob.xyz) {
45
        return 301 https://$host$request_uri;
46
    } # managed by Certbot
47

          
48
    listen 80;
49
    server_name mahboob.xyz;
50
    return 404; # managed by Certbot
51
}
52

          
53
server {
54
    listen 80 default_server;
55
    listen [::]:80 default_server;
56
    server_name _;
57
    root /var/www/html;
58
    return 301 https://$host$request_uri;
59
}



You can grab the code from the Github repository and play the game here. Have fun, and sharpen your vocabulary.

Picasa Web Albums mobile app Flask (web framework) file IO Clue (mobile app) Web application

Opinions expressed by DZone contributors are their own.

Related

  • Flask Web Application for Smart Honeypot Deployment Using Reinforcement Learning
  • The Cutting Edge of Web Application Development: What To Expect in 2024
  • Building a Tool To Generate Text With OpenAI’s GPT-4 Model
  • Ensuring Security and Compliance: A Detailed Guide to Testing the OAuth 2.0 Authorization Flow in Python Web Applications

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook