A basic understanding of Python and JavaScript are needed to follow this tutorial.

Flask is a really cool Python framework for building web applications. One of its major selling points is how simple it is to get started on building apps with it. In this tutorial, we will build a simple Flask CRUD application, and add realtime functionality to it using the superpowers Pusher grants us.

Our CRUD app is a simple realtime to-do list app that can find use in distributed teams, for example to manage their deliverables.

Here is what the final app will look like:

Prerequisites

To follow along properly, basic knowledge of Python, Flask and JavaScript (ES6 syntax) is needed. You will also need the following installed:

Virtualenv is great for creating isolated Python environments, so we can install dependencies (like Flask) in an isolated environment, and not pollute our global packages directory. To install virtualenv:

pip install virtualenv

Setup and Configuration

Installing Flask

Now, we can create our project folder, activate a virtual environment in it, and install Flask. To activate a virtual environment:

mkdir realtime-todo cd realtime-todo virtualenv .venv source .venv/bin/activate

To install Flask:

pip install flask

Setting up Pusher

We will be using Pusher to power our realtime updates. Head over to Pusher.com and register for a free account, if you don’t already have one. Then create an app on the dashboard, and copy out the app credentials (App ID, Key, Secret and Cluster). It is super straight-forward.

We also need to install the Pusher Python Library to communicate with Pusher on the backend:

pip install pusher

File and Folder Structure

We will keep things super simple and will only create a couple of files. Here’s the file/folder structure used:

├── realtime-todo ├── app.py ├── static └── templates

The static folder will contain the static files to be used as per Flask standards, and the templates folder will contain the HTML templates for the app. App.py is the main entrypoint for our app and will contain all our server-side code.

Building Our App Backend

Next, we will write code to show a simple view and create endpoints for adding, updating and deleting our to-do's. We will not be persisting the data to a database, but will instead use Pusher events to broadcast data to all users subscribed to our channel.

Updating app.py :

# ./app.py from flask import Flask, render_template, request, jsonify from pusher import Pusher import json # create flask app app = Flask(__name__) # configure pusher object pusher = Pusher( app_id='YOUR_APP_ID', key='YOUR_APP_KEY', secret='YOUR_APP_SECRET', cluster='YOUR_APP_CLUSTER', ssl=True ) # index route, shows index.html view @app.route('/') def index(): return render_template('index.html') # endpoint for storing todo item @app.route('/add-todo', methods = ['POST']) def addTodo(): data = json.loads(request.data) # load JSON data from request pusher.trigger('todo', 'item-added', data) # trigger `item-added` event on `todo` channel return jsonify(data) # endpoint for deleting todo item @app.route('/remove-todo/<item_id>') def removeTodo(item_id): data = {'id': item_id } pusher.trigger('todo', 'item-removed', data) return jsonify(data) # endpoint for updating todo item @app.route('/update-todo/<item_id>', methods = ['POST']) def updateTodo(item_id): data = { 'id': item_id, 'completed': json.loads(request.data).get('completed', 0) } pusher.trigger('todo', 'item-updated', data) return jsonify(data) # run Flask app in debug mode app.run(debug=True)

In the code block above, after importing the needed modules and objects and initialising a Flask app, we initialise and configure Pusher. Remember to replace YOUR_APP_ID and similar values with the actual values gotten from the Pusher dashboard for your app. With this pusher object, we can then trigger events on whatever channels we define.

A clear example of this is seen in the addTodo() procedure, where we trigger an item-added event on the todo channel with the trigger method. The trigger method has the following syntax:

pusher.trigger('a_channel', 'an_event', {'some': 'data'})

You can find the docs for the Pusher Python library here, to get more information on configuring and using Pusher in Python.

In the code above, we also created an index route which is supposed to show our app view by rendering the index.html template. In the next step, we will create this view and start communicating with our Python backend.

Creating Our App View

Now, we create our main app view in ./templates/index.html . This is where the interface for our app will live.

First we will pull CSS for TodoMVC apps to take advantage of some pre-made to-do list app styles, and store the folder in the ./static folder.

Next, we can write the basic markup for the view:

< html > < head > < link rel = "stylesheet" href = "/static/todomvc-app-css/index.css" > < title > Realtime Todo List </ title > </ head > < body > < section class = "todoapp" > < header class = "header" > < h1 > Todos </ h1 > < input class = "new-todo" placeholder = "What needs to be done?" autofocus = "" onkeypress = "addItem(event)" > </ header > < section class = "main" > < ul class = "todo-list" > </ ul > </ section > < footer class = "footer" > </ footer > </ section > </ body > </ html >

In the above markup, notice we added an addItem() function to be called onkeypress for the .new-todo input. In the following steps we will define this function, as well as other JavaScript functions to handle the basic app functions and interact with our Python backend.

Creating, Removing and Updating To-do Items

Now, we can add the JavaScript code to interact with the to-do items. Whenever an item is to be added, removed or updated, we will make API calls to our backend to affect those changes. We will do this with the simple and intuitive Fetch API:

< html > < script > function addItem ( e ) { if (e.which == 13 || e.keyCode == 13 ) { let item = document .querySelector( '.new-todo' ); fetch( '/add-todo' , { method : 'post' , body : JSON .stringify({ id : `item- ${ Date .now()} ` , value : item.value, completed : 0 }) }) .then( resp => { item.value = "" }); } } function removeItem ( id ) { fetch( `/remove-todo/ ${id} ` ); } function toggleComplete ( elem ) { let id = elem.dataset.id, completed = (elem.dataset.completed == "1" ? "0" : "1" ); fetch( `/update-todo/ ${id} ` , { method : 'post' , body : JSON .stringify({ completed }) }); } function appendToList ( data ) { let html = ` <li id=" ${data.id} "> <div class="view"> <input class="toggle" type="checkbox" onclick="toggleComplete(this)" data-completed=" ${data.completed} " data-id=" ${data.id} "> <label> ${data.value} </label> <button class="destroy" onclick="removeItem(' ${data.id} ')"></button> </div> </li>` ; let list = document .querySelector( ".todo-list" ) list.innerHTML += html; }; </ script > </ body > </ html >

Note: The JavaScript Fetch API is great for making AJAX requests, although it requires a polyfill for older browsers. A great alternative is axios.

In the above block of code, we define 4 functions to help us interact with the items on our to-do list. The addItem() function makes a POST API call to add a new item to the to-do list, with the value from our input field, we also try to mock a unique ID for each item by assigning them a value of item-${Date.now()} (ideally this would be implemented by our data store, but it is beyond the scope of this tutorial). Lastly, we assign an initial state of 0 to the completed property for each item, this is to show that the item is just added, and has not yet been completed.

The removeItem() function makes a request to delete an item, while the toggleComplete() function makes a request to update the completed property of an item. An appendToList() helper function is also defined to update our to-do list with new items, this helper function will be used in the next step when we start listening for events.

Listening For Events

In this step we will listen for events from Pusher, and update our app view based on the data received. Updating index.html :

<!-- ./templates/index.html --> <html> <!-- .// --> <script src="https://js.pusher.com/4.1/pusher.min.js"></script> <script> // Enable pusher logging for debugging - don't include this in production Pusher.logToConsole = true; // configure pusher const pusher = new Pusher('YOUR_APP_KEY', { cluster: 'eu', // gotten from Pusher app dashboard encrypted: true // optional }); // subscribe to `todo` public channel, on which we'd be broadcasting events const channel = pusher.subscribe('todo'); // listen for item-added events, and update todo list once event triggered channel.bind('item-added', data => { appendToList(data); }); // listen for item-removed events channel.bind('item-removed', data => { let item = document.querySelector(`#${data.id}`); item.parentNode.removeChild(item); }); // listen for item-updated events channel.bind('item-updated', data => { let elem = document.querySelector(`#${data.id} .toggle`); let item = document.querySelector(`#${data.id}`); item.classList.toggle("completed"); elem.dataset.completed = data.completed; elem.checked = data.completed == 1; }); // ... </script> </body> </html>

The first thing we have to do here is to include the pusher-js library to help us communicate with the Pusher service. Next, we initialise the Pusher service by passing in our App Key, and some other options — for a full list of configuration options, you can check the docs here.

After successfully initialising Pusher and assigning it to the pusher object we can then subscribe to the channel from which we want to receive events, in our case that’s the public todo channel:

const channel = pusher.subscribe('todo');

Note: Pusher provides various types on channels, including Public, Private and Presence channels. Read about them here.

Finally, we bind the various events we’re listening for on the channel. The bind() method has the following syntax - channel.bind(event_name, callback_function)

Optionally, we can add a loader to the page, which would show whenever a request is made. The final index.html file would look like this, and our app should be ready now!

To run our app:

python app.py

And here is what the demo looks like:

Conclusion

In this tutorial, we have learned how to build a Python Flask project from scratch and add realtime functionality to it using Pusher and Vanilla JavaScript. The entire code for this tutorial is hosted on GitHub.

There are many other use cases for adding realtime functionality to Python applications. Do you have any more improvements, suggestions or use cases? Let us know in the comments!