Realtime Polling web application with Python FastApi WebSockets, PyMongo, HTML & JavaScript
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:
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.
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.
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.
Here’s the output after hitting http://localhost:8000
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
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 exdef 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 :
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.
So, I’ve randomly clicked pizza & burger buttons in our html page and I got these votes stored in our 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
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 ☕😀