Realtime Polling web application with Python FastApi WebSockets, PyMongo, HTML & JavaScript

Vinay_NVD
Nerd For Tech
Published in
9 min readJun 17, 2021

Let’s build a full stack real time voting web application by using these
1. Python’s FastApi
2. Websockets.
3.
PyMango
4. HTML, CSS, JavaScript
.
5. chart.js (For polling graph, we are going to use).

Hold On! Did I just said WebSockets 🤨?

Here’s why WebSockets :

We all know that we can use Http calls to send user’s poll to the server. But if you want to build a real-time web application we need to use WebSockets. Let me explain this clearly:

Http vs WebSockets Protocols work flow in realtime polling application
Http Protocol workflow

When we use Http protocol as our data transport mechanism, Http protocol is Uni-Directional. It means a server will only send data if requested by client & close the connection.
i.e., A new TCP connection will be created for every HTTP request.

WebSockets

WebSockets are Bi-Directional connection, It creates a stateful protocol, It means connection between client & server can be maintained alive. So we don’t need to create a new TCP connection for every request to server, instead we can use the same connection.

Http vs WebSockets, Websockets Protocol work flow
WebSocket Protocol workflow

I’ve got this information from this wonderful article (What is web socket and how it is different from the HTTP?), check this article

Polling website with FastAPI Websockets, Html & JS

Let’s build a Pizza vs Burger voting website with Python’s FastAPI WebSockets & Html, js (especially chart.js).

Here’s the an image of our output.

Pizza vs burger realtime polling web application built with Python’s Fastapi, websockets, pymongo, html, css & javascript
Pizza vs Burger real time polling application sample output

We’ll first build our backend

Note : Before creating our project. I just want to let you know that in this project, I’m going to use only 3 files.
1. application.py (Here I’ll write websocket apis, it’s Middleware).
2. pizzaVsburger.html (Here I’ll write the html code, which shows bar graph as above image, it’s Frontend).
3. main.js (Here’ll our javascript functionalities will be written).
4. I’m going to use MongoDB as our database, to store our user votes. (Backend)

I’ll be repeatedly modifying the code only in these 3 files. Any code block you see here, they belong to these files only. I’ll also mention the file name for which the code block belongs to.

Setup project’s virtual environment

Let’s create our project’s folder named pizzaVsburger. In that folder we are going to setup our project’s virtual environment.
I assume, you already have python & virtualenv installed in your local machine. If not please install them by following this guide.
Then we’ll create our backend virtual environment, where all your project’s backend python dependencies will be installed in.
Run this command :

virtualenv backend

After running this command, backend folder will be created with 2 sub folders (Lib & Scripts) & a .gitignore & pyvenv.cfg files

We need to install few dependencies, so that we can write our backend apis. Run these set of commands in your command prompt.

backend\Scripts\activate
pip install fastapi "uvicorn[standard]" numpy

FastAPI to write our backend APIs & Uvicorn to create ASGI server for our application.

Writing WebSocket APIs using FastAPI

Next we need to create an application.py file in backend folder, Here we are going to write our WebSocket APIs.

I’m setting up connection to MongoDB client so that we can store user votes & initialize the application.

from fastapi import FastAPI
from pymongo import MongoClient

# App initialization
app = FastAPI()

# Setting up connection with MongoDB
client = MongoClient("mongodb://localhost:27017/")
database = client["pizzaVsburger"]
# database's table which stores the user votes.
collection
= database["votes"]

We have connection with MongoDB, next we need to have a default endpoint, Which returns our voting html page & I name it as pizzaVsburger.html.

So we need to import & use Jinja2Templates from fastapi.templating for returning html file as output.

from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from pymongo import MongoClient

# App initialization
app = FastAPI()

# Setting up connection with MongoDB
client = MongoClient("mongodb://localhost:27017/")
database = client["pizzaVsburger"]
# database's table which stores the user votes.
collection
= database["votes"]
# Templates directory, in which our html & javascript files are stored.
templates = Jinja2Templates(directory="templates")
app.mount("/templates", StaticFiles(directory="templates", html=True), name="templates")
# API which returns HTML file as response.
@app.websocket("/")
async def get(request: Request):
# Here in list 1 represent pizza's vote & 0 represents burger's.
votes = [1,0]
return templates.TemplateResponse("pizzaVsburger.html", {'request': request, 'votes', })

Let’s run the above code, check the following commands below.

uvicorn application:app --reload

After running the above code, application.py file runs on the default port 8000.

Polling application middle ware fastapi startup in local
Running fastapi in local with uvicorn

Here’s the output after hitting http://localhost:8000

Error in realtime polling application built with Python FastApi, Websockets, Pymongo, Html, Javascript & css
Error in Pizza Vs Burger Polling application

Above code works, if we have pizzaVsburger.html file in templates directory. So we need to create templates folder in backend directory & add pizzaVsburger.html file in it.

Let’s write HTML code for our voting application

After creating pizzaVsburger.html file, add this code. It’s front end part of our project.

<html lang="en">
<head>
<title>Pizza Vs Burger</title>
<script src="./templates/js/main.js"></script>
</head>
<body>
<div style="width:50%; display: block; margin: 0px auto; text-align: center;">
<h1>Legendary Battle</h1>
<d
</body>
</html>

Let’s refresh the page, & then we get this one

Basic Html page for pizza vs burger polling web application. Running with Python Fastapi, websockets, pymongo, html, css & javascript
Basic Html page for pizza vs burger polling web application.

Here comes the actual work. We have our websocket & html page ready, & the remaining work left for us is a bar graph, which shows the votes & an endpoint to store our data(user votes) in MongDB.

Let’s write an API, which store votes in our Votes table of MongoDB. Add these below lines in the application.py file

@app.websocket("/sendVote")
async def user_vote(websocket: WebSocket):
"""
This is a websocket method, which responds to the call /sendVote.
Client will use this websocket api as a medium to send their votes.
"""

await websocket.accept()
try:
while True:
data = await websocket.receive()
if data['type'] != 'websocket.disconnect':
vote = {
'pizza': 1,
'burger': 0
} if data['text'] == 'pizza' else {
'pizza': 0,
'burger': 1
}
add_vote(vote)

await websocket.send_json(data)
except Exception as ex:
return ex
def add_vote(vote):
"""
This method stores the user votes in 'votes' table of pizzaVsburger database in MongoDB
"""

collection.insert_one(vote)

From the above code, when we hit the above endpoint “/sendVote”, we send ‘pizza’ or ‘burger’ as data[‘text’]’s value from our javascript.
Our javascript code in main.js.

var ws = new WebSocket("ws://localhost:8000/sendVote");/**
Function will be called on clicking vote button from pizzaVsburger.html
*/
function myVote(voteTo) {
var vote = voteTo === 'pizza' ? 0 : 1;
pollingChart.data.datasets[0].data[vote] += 1;
pollingChart.update();
sendMessage(voteTo);
}

/**
Function will send the vote to server by using websocket.
*/
function sendMessage(votedTo) {
ws.send(votedTo);
event.preventDefault();
}

We call myVote(‘string’) method from our html page by clicking a button.

<html lang="en">
<head>
<title>Pizza Vs Burger</title>
<script src="./templates/js/main.js"></script>
</head>
<body>
<div style="width:50%; display: block; margin: 0px auto; text-align: center;">
<h1>Legendary Battle</h1>
<br>
<button onclick="myVote('pizza')">Pizza</button>
<button onclick="myVote('burger')">Burger</button>
<div>
</body>
</html>

Output :

Pizza vs Burger html page, running with Python’s fastapi, websockets, pymongo, html, css & javascript
Working pizza vs burger web application, without chart in it

If I hit Pizza button, it’s going to call myVote(‘pizza’) in main.js file. Then myVote method is going to call sendMessage(vote) method, which actually hits the websocket’s api “/sendVote” with data[‘text’] as ‘pizza’.
Then in server, sendVote api is going to add “1 in pizza column” & “0 in burger column”.

I’m using MongoDB Compass, to view my votes in our database.

MongoDB compass showing database tables
MongoDB Compass which shows pizzaVsburger database
Viewing collections in MongoDB compass
votes collection stored in pizzaVsburger database in MongoDB

So, I’ve randomly clicked pizza & burger buttons in our html page and I got these votes stored in our MongoDB

Viewing data in MongoDB compass, Data stored in Mongodb using python’s pymongo
User votes stored in votes collection (MongoDB)

Cool it’s working right !! 😎

Let’s create a Bar graph in our html page using Chart.js

We have voting buttons in frontend, API as middleware & MongoDB as backend. Now we need to visualize the votes in our pizzaVsburger.html page.

I’m using chart.js (It’s an open source HTML5 charts lib). Chart.js is an awesome javascript library, which provides simple, flexible javascript charts for website.

To start, we need to add these lines in our pizzaVsburger.html file.

<html lang="en">
<head>
<title>Pizza Vs Burger</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
.btn-primary {
margin: 6px;
background: #fff;
color: #000;
border: 1px solid black;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/emn178/chartjs-plugin-labels/src/chartjs-plugin-labels.js"></script>
<script src="./templates/js/main.js"></script>
</head>
<body translate="no">
<div style="width:50%; display: block; margin: 0px auto; text-align: center;">
<h1>Legendary Battle</h1>
<br>
<canvas id="mycanvas"></canvas>
<div class="row">
<button class="btn btn-primary col" onclick="myVote('pizza')">
Vote Pizza
</button>
<button class="btn btn-primary col" onclick="myVote('burger')">Vote Burger</button>
</div>
</div>
<script>
setUpChart({{ votes }})
</script>
</body>
</html>

In the above code I used bootstrap & few javascript files, which are provided by chart.js.
We need to send {{ votes }} from application.py’s default get method, & to show bar graph for our votes, modify main.js file as below.

pollingChart;

function setUpChart(dataVotes) {
var ctx_live = document.getElementById("mycanvas");
var myChart = new Chart(ctx_live, {
type: 'bar',
data: {
labels: ['Pizza', 'Burger'],
datasets: [{
label: 'Pizza Vs Burger',
data: dataVotes,
fill: false,
backgroundColor: ['#00eefa','#37dbff'],
borderColor: '#000',
borderWidth: 1
}]
},
options: {
plugins: {
labels: {
render: 'image',
textMargin: -130,
images: [
{
src: 'https://image.freepik.com/free-vector/cute-pizza-cartoon-vector-icon-illustration-fast-food-icon-concept-flat-cartoon-style_138676-2588.jpg',
width: 120,
height: 120
},
{
src: 'https://image.freepik.com/free-vector/cute-burger-holding-knife-fork-cartoon-fast-food-icon-concept-isolated-flat-cartoon-style_138676-2204.jpg',
width: 120,
height: 120
}
]
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});

pollingChart = myChart;
}

From the above code, we are setting up our bar graph & adding images over our bar graph from freepik.com (You can get free & premium images from this site).

In the above code, function setUpChart(dataVotes) is going to take dataVotes as input. Here dataVotes value is going to be an array with 2 values.
dataVotes = [pizza vote count, burger vote count].
& We retrieve them from websocket api. Let’s add these lines in our application.py

def get_votes():
"""
Method returns all the votes from 'votes' table of pizzaVsburger database in MongoDB
"""

x = [vote for vote in collection.find()]
return x

& we modify our user_vote method & default get method to this

@app.get("/")
async def get(request: Request):
"""
GET method to return html page, when user hit's the endpoint '/'.
This method first gets all the votes from the votes table from Mongo Database
& counts the votes for Pizza & Burger & return html file the votes in a single list.
output format: votes = [(pizza votes),(burger votes)]
"""

votes = [[x['pizza'], x['burger']] for i, x in enumerate(get_votes())]
user_votes = np.array(votes)
response = np.sum(user_votes, 0)
votes = list(response)
return templates.TemplateResponse("pizzaVsburger.html", {'request': request, 'votes': votes})"""-------------------------------------------------------------"""@app.websocket("/sendVote")
async def user_vote(websocket: WebSocket):
"""
This is a websocket method, which responds to the call /sendVote.
Client will use this websocket api as a medium to send their votes.
"""

await websocket.accept()
try:
while True:
data = await websocket.receive()
if data['type'] != 'websocket.disconnect':
vote = {
'pizza': 1,
'burger': 0
} if data['text'] == 'pizza' else {
'pizza': 0,
'burger': 1
}
add_vote(vote)
else:
data = get_votes()
await websocket.send_json(data)
except Exception as ex:
return ex

Finally this is the output

Realtime polling application built with Python’s Fastapi, websockets, pymongo, Html, css & javascript
Final output for pizza vs burger gif image

Github repo link for this project is here 😀.

Conclusion :

We built awesome realtime pizza vs burger polling website. You can build awesome, realtime web applications in this way. Come up with your cool real time web application ideas & comment down here.

If you loved this article & wanted to show some love & support, please buy me a coffee ☕😀

Buy a coffee for vinaynvd
https://www.buymeacoffee.com/vinaynvd

--

--

Vinay_NVD
Nerd For Tech

Full Stack Dev 😎 | Angular | Spring MVC | Spring security | Python | AI | Data Science | AWS