Web Development with Python and Vue.js Components

Web Development with Python and Vue.js Components

At Reverie Labs, we use Python to power our machine learning model and data pipelines. We also use Python-based web frameworks such as Django for our internally deployed web applications (and for the less complicated ones, we use Streamlit!).

In this blog post, we will be describing how we utilize Vue.js to enhance our web applications. Specifically, we will:

  1. Craft Vue components to asynchronously call backend functions and update the frontend
  2. Import those Vue components into a Vue instance for use in a Flask/Django template
  3. Expose the data variables of the Vue component as part of an external form

The lessons will build on each other, culminating in a Flask+Vue.js web application inspired by medicinal chemistry. It’ll look something like this:

The green input box is the Vue component

Before Vue.js

To trigger API calls and manipulate the frontend, you can use jQuery to communicate with the backend asynchronously. jQuery is easily embedded within Django’s HTML template files.

While jQuery is flexible and functional, it can grow to be code bloat. For example, each HTML object must be identified and changes must be specified line by line. Often, you see template files have more jQuery than HTML!

For complex web apps, frameworks like Vue are more suitable, as they simplify the code needed to write reactive web pages. A core feature of Vue is the ability to define and utilize Vue Components.

What is a Vue Component?

Vue components are self-contained elements that can render HTML/CSS, store data within Javascript variables, and make GET/POST calls. Once defined, they are callable as HTML tags. For example, within existing HTML a Vue component can be rendered like below:

Check out the Vue documentation for a more in-depth walkthrough of Vue components.

Here, the Vue component tags <vue-component></vue-component> are responsible for rendering a part of the frontend that takes user input. The components are usually defined in a .vue file and require Webpack to serve.

The challenge of writing Vue Components within Python web frameworks

As a startup, we prefer to limit the number of technologies we employ and thus have to maintain. Thankfully like jQuery, Vue is importable as a CDN library removing the need to introduce a frontend server like Node.js.

Initially, it was unclear how Vue components should be written within a CDN-based frontend. We cannot use .vue files since we are relying on a CDN library. In addition, Vue components should be defined in their own file so that they can be reused across multiple templates.

After multiple iterations, we have figured out our preferred method for defining Vue components, and it's pretty straightforward!

We will demonstrate our method with a web application that displays the characteristics of proteins, a helpful tool for medicinal chemists. The Vue component will interface with the KLIFS database to select and display metadata about specific proteins using their PDB id.

What are PDBs?

To design drugs that inhibit disease-causing proteins, we must understand their structure.  A PDB is a file format that stores 3-D structural information about a protein. You can find more information about PDBs and how Reverie stores, aligns and organizes PDB files in our blog post.

Collections of PDBs are available to download online, like KLIFS — an open-source database of kinase PDBs. The KLIFS database summarizes the protein structure with metadata such as DFG conformation. This metadata classifies the structure of the protein into binary options (DFG is either in or out).

Certain subtypes of drugs, specifically kinase inhibitors, may prefer binding to the DFG in conformation while others have a preference for binding to the DFG out conformation. For example, the drug Imatinib (Gleevec), used to treat Leukemia, is known as a type-II kinase inhibitor since it inhibits proteins with a DFG-out conformation. There are many other protein metadata and drug classifications (Roskoski Jr, 2016), but we will focus on two for our demonstration: DFG and aC-Helix.

Demo

Now that we understand the importance of protein metadata, it would be useful to have a tool that can automatically retrieve this information from KLIFS. We will build such a tool within a Vue component that displays the metadata of a searched protein.

First, we must create the Flask backend that will query KLIFS and render the frontend HTML templates. While we are writing the demo in Flask, the Vue component method will also work in a Django application.

Flask Application

Let's start with the following Flask application that queries the KLIFS API and renders our frontend templates app.py:

from flask import Flask, render_template, request
import requests

app = Flask(__name__)

'''Backend'''
# Called by Vue to retrieve the properties of each PDB from KLIFS
@app.route('/pdb_id/<pdb_id>', methods=['GET'])
def klifs_pdb_data(pdb_id):
    # API Endpoint
    URL = "https://klifs.net/api/structures_pdb_list"
    
    # PDB we would like to query for
    PARAMS = {'pdb-codes': pdb_id}

    # Send the GET request to the API and retrieve any returned data
    res = requests.get(url = URL, params = PARAMS)
    data = res.json()

    # If an error occured, return the error
    if res.status_code == 400:
        return data[1], 400
        
    # Return the first set of PDB characteristics
    return data[0]
    
'''Frontend'''
# Loads Vue component that renders the KLIFS selector
@app.route("/frontend/")
def vue_selector():
    return render_template('frontend.html')
app.py

The klifs_pdb_data function uses a GET request to retrieve the metadata of a protein from KLIFS using the pdb_id. After checking for errors, it returns the metadata as a JSON object. The documentation regarding this API endpoint and other KLIFS API interactions can be found here: https://klifs.net/swagger/#/Structures/get_structures_pdb_list

The vue_selector function is responsible for rendering the HTML file frontend.html. This will be the template that imports our Vue component. Let’s now define a basic Vue component that we will expand on.

Basic Vue Component

We will be creating two HTML files:

  • vue_component.html defines the Vue component
  • frontend.html imports the component into a Vue instance.

Let's write the first file vue_component.html:

<script type="text/x-template" id="vue-component-template">
<div>
    Hello World
</div>
</script>

<script>
var vue_component = {
    template: '#vue-component-template',
    delimiters: ['[[', ']]'],
    data: function () {
        return {
        }
    },
    methods: {
    }
}
</script>
vue_component.html

There are two sets of script tags. The first defines the HTML structure of the component; ours displays “Hello World” to the screen. The second set of script tags initializes a Vue component along with its properties and methods. We'll leave them empty for now.

The template property of the Vue component imports the HTML structure using the script tag’s id #vue-component-template. Without this script tag separation, we would have to write HTML as a string within the Vue component. With a more complex component, this would prove difficult to read.

To learn more Vue component definitions and their tradeoffs, check out this Vue.js Developers blog post.

Calling the Vue Component

We now have a defined Vue component! To display it, we will call it within Flask's frontend template. We will do that by initializing a Vue application and importing the component within a new file frontend.html:

<!doctype html>

<title>Simple Vue Component</title>
<head>
    <!-- Load required Bootstrap and BootstrapVue CSS -->
    <link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
    <!-- Load Vue followed by BootstrapVue and Axios-->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
    <div id="app">
        <vue-component></vue-component>
    </div>
</body>

{% include 'vue_component.html' %}
<script>
    new Vue({
        el: '#app',
        components: {
            'vue-component' : vue_component
        } 
    })
</script>
frontend.html

This file includes 3 core elements, the head, body, and script. The head imports the required libraries Vue, Bootstrap, and Axios. The body renders the HTML template along with the Vue component. Finally, after including the vue_component.html file, the script creates a Vue instance named app and imports the Vue component.

Note the difference between the Vue component, which creates reusable elements, and the Vue instance, which loads the components into a Vue application with a name defined by el.

In order to connect the HTML template in the body element with Vue, we create a div tag id’d with the name of our Vue instance <div id="app"></div>. In this way, our Vue component can be rendered within the tag: <vue-component></vue-component>!

Running the Application

Install Flask within your Python environment, and start it using:

FLASK_ENV=development flask run

Then navigate to the URL defined by our flask application localhost:5000/frontend and you will see:

That's it! We have successfully defined a Vue component and have imported it into our Flask template.

Let's now expand on this example and create a Vue component that allows users to select and view protein metadata from KLIFS.

KLIFS Vue Component Frontend

On the vue_component.html file replace Hello World with the following BootstrapVue components to record user input and render an interactive button:

<script type="text/x-template" id="vue-component-template">
<div>
    <b-form v-on:keydown.enter.prevent>
        <b-form-input
            v-model="pdb_id"
            placeholder="Enter PDB ID"
            required
        ></b-form-input>
        <b-button variant="secondary" size="sm" v-on:click="getPDB">
            Retrieve PDB metadata
        </b-button>
    </b-form>
</div>
</script>
...
vue_component.html

Refresh localhost:5000/frontend, and the UI now contains:

This is what Vue tooling looks like in action. The v-on property prevents the user from accidentally submitting a form. v-model is a handy tool for linking the content of HTML fields with a Vue variable. Here we use it to tag the content of the text field with the variable pdb_id.

We also use some BootstrapVue properties such as the placeholder which is what the text field will display when empty. Also, required prevents a blank input from being submitted to the form.

The button, when clicked, will run the getPDB function. Right now, it doesn't do anything since we haven't defined the function.

Define the Backend Interaction with Axios

Let's now define the function along with data variables. Within the same file, vue_component.html, replace the second script tag with the following:

...
<script>
var vue_component = {
    template: '#vue-component-template',
    delimiters: ['[[', ']]'],
    data: function () {
      return {
        pdb_id: "",
        pdb_exists: false,
      }
    },
    methods: {
        getPDB() {
            axios.get("http://localhost:5000/pdb_id/" + this.pdb_id)
                .then(response => {
                    this.DFG = response.data.DFG;
                    this.aC_helix = response.data.aC_helix;
                    // Show the list of attributes
                    this.pdb_exists = true;
                }).catch(e => (
                    console.log("There was an error!")
                ))
        }
    }
};
</script>
vue_component.html

This fully defines the backend reactivity of our Vue component. Let's examine each new part.

  • delimiters: Vue has a template notation similar to Jinja in Django and Flask where it uses double curly braces ({{, }}) to write backend variables into the frontend. We change Vue's notation to use double brackets ([[, ]]) to prevent collisions.
  • data: We initialize two variables pdb_id and pdb_exists. As we have seen, pdb_id is tagged by the v-model of the BootstrapVue component and represents the text field content. Here we set its default value to an empty string. The other variable, pdb_exists, is a boolean set to false to indicate we haven’t retrieved the PDB metadata yet.
  • methods: This defines the getPDB function. It uses Axios, axios.get, to query our Flask backend for the KLIFS GET request. this refers to the current Vue component. We use it to get the content of the text field this.pdb_id to pass as a parameter of the GET request.

Upon completion of the GET request, we parse the DFG and aC_helix values and insert them into new Vue data objects: this.DFG and this.aC_helix. Finally, we set the variable pdb_exists to true, indicating that the PDB id has successfully returned metadata.

Display the Backend Response

Finally, let's display our newly defined variables to the user. Within the first set of script tags, after the form, add:

...
<ul v-if="pdb_exists">
    <li>DFG: [[ this.DFG ]]</li>
    <li>aC_helix: [[ this.aC_helix ]]</li>
</ul>
vue_components.html

We are using plain HTML, but are still able to enhance it with Vue. The v-if Vue property controls if the HTML is displayed or not. Here we link it to the value of the Vue variable pdb_exists. Initially, it's false, but after a successful KLIFS query, it becomes true. Once it becomes true, the metadata (DFG, aC_helix) is displayed as bullet points.

The variables from the backend are printed using Vue's delimiters [[, ]].

Refresh localhost:5000/frontend and input a PDB id. After clicking the button, you will be able to see the corresponding metadata:

Great! We now have a frontend component that asynchronously queries the backend template. We're displaying the metadata for 2HYY. 2HYY is a PDB of the protein kinase ABL1. This is a protein that the drug Imatinib, the type-II inhibitor mentioned earlier, can bind to. As expected, the protein is in a DFG-out conformation since Imatinib is a type-II inhibitor.

Expose Data from Vue Components

In some cases, it may be desirable to expose the variables of a Vue component to another process, such as an existing HTML form. This would allow a developer to construct an entire UI in HTML/CSS while abstracting the more complicated inputs and methods to imported Vue components.

Let’s demonstrate how to do this by adjusting our current Vue Component to expose the values of DFG and aC_helix. Within the vue_component.html right where we left off under the bullet points in the first set of script tags, add the following div:

...
<div v-if="pdb_exists">
    <input type="hidden" v-model="DFG" name="vue_dfg">
    <input type="hidden" v-model="aC_helix" name="vue_ac_helix">
</div>

Since Flask cannot access a component’s data variables, we can instead expose them as hidden HTML input fields. After the PDB metadata has been returned successfully, v-if="pdb_exists", the data variables specified by v-model are placed within hidden HTML inputs. As a result, when the user submits a form containing this Vue component, the component's data variables will appear as values under the name property.

We have included the entire code that demonstrates our KLIFS Vue component within a form submission to Flask as an example in the Github repository. This is the complete wireframe:

This example submits the entire HTML form along with the protein metadata from the Vue component (DFG, aC_helix) to the backend. A process could use the data to then match a drug inhibitor type to the protein. This example could be expanded further to retrieve the 3-D file from KLIFS using Vue and submit it to a machine learning model for predictions.

Closing

That’s it! To recap, you are now able to:

  1. Craft Vue components to asynchronously call backend functions and update the frontend
  2. Import those Vue components into a Vue instance for use in a Flask/Django template
  3. Expose the data variables of the Vue component as part of an external form

Please find all the code examples in our Github repository: https://github.com/JustinChavez/vue-components-blog. Feel free to fork the repo -- we’d love to see some creative applications of this tutorial.

If these types of work and problems excite you, check out our careers page! The team includes a mix of industry experienced engineering and biotech professionals. We're actively hiring engineers across our tech stack and chemistry team, including Full Stack Engineers, Machine Learning Engineers, and Senior Data Scientists, to work on exciting challenges critical to our approach to developing life-saving cancer drugs. You will work with a YC-backed team that is growing in size and scope. You can read more about us at www.reverielabs.com, and please reach out if you're interested in learning more.