Back

How to upload PDF files to Azure Blob Storage with Vue and Python Flask

March 24, 2022 6 minute read
Red and blue sky with cloud
Source: Unsplash

In this article we’ll take a look at uploading PDF files to Azure Blob Storage using Vue and Python Flask. This is a common use case I’ve come across for document storage. Although we’ll be uploading PDFs in this article, the same approach can be used for files of any kind.

Getting started

We’re going to use the same Vue Flask template I used from another article How to query a database with Python Flask and download data to CSV or XLSX in Vue. The template is in this public GitHub repository from gtalarico.

Once you’ve cloned or downloaded the repo, setup a virtual environment with pipenv and install the packages that will be needed below.

cd flask-vuejs-template-master
python -m pip install pipenv
python -m pipenv install --dev
python -m pipenv install flask-restx azure-storage-blob
python -m pipenv shell

Since this template uses Flask-RESTPlus and we're using Flask-RESTX which is a community driven fork of it, go ahead and replace all references to 'flask_restplus' with 'flask-restx'.

We'll be using the azure-storage-blob package which has a quickstart guide from Microsoft. There are similar packages available for other languages too, including Java, C# and .NET, JavaScript, C++, Go and more.

Now that the Python packages are installed, let's install and upgrade the Vue dependencies with Yarn, and build the Vue dist directory.

yarn install --dev
yarn upgrade
yarn build

If everything went smoothly, you should be able to run both the backend and frontend dev servers. Run python run.py and from another terminal window in the same directory run yarn serve. You should see the app running at http://localhost:8080/ 👍

Set up Azure Blob Storage container

Beginning with the end in mind, we'll need a place to store files in Azure. So the first job is to setup a Storage Account for that in Azure. This article from Microsoft goes over the process of setting one up. You head to the Azure portal and search for "storage account" then hit Create.

Make sure you delete this Storage Account resource after testing as you may incur costs if you don't. If in any doubt always check the pricing calculator or Azure Blob Storage pricing page from Microsoft.

Once finished deploying you'll need to create a container and grab the credentials as we'll need them later on!

To create a container, go to the Storage Account resource and hit the add container button. We'll use this new container to store the PDF files.

Then head to Access keys under Security + networking and hit Show keys. Copy the storage account name, the keys and connection strings. You should only need the Connection string under key1 to connect with the Python SDK though. Never hurts to have backup credentials.

Create form to upload file in Vue

Inside src/views/Home.vue we first want a very basic outline of our file upload form. Substitute the template tags for this new template creating the form.

src/views/Home.vue
<template>
  <div class="container">
    <div>
      <label>File
        <input type="file" ref="fileInput" accept="image/*,.pdf" @change="handleFileUpload($event)"/>
      </label>
      <br/>
      <br/>
      <button v-on:click="submitFile()">Submit</button>
    </div>
  </div>
</template>

We then need to implement the handleFileUpload and submitFile methods. The first will allow us to stage a file, and the second will allow us to submit and send that file with Axios to the Python API endpoint at /api/upload we'll create later.

src/views/Home.vue
<script>
/* eslint-disable */
import axios from 'axios'

export default {
  data () {
    return {
      file: null
    }
  },
  methods: {
    /**
     * Uploads file to server.
     * @param {Event} event The form change event with the file to be uploaded.
     */
    handleFileUpload(event) {
      this.file = event.target.files[0];
    },
    /**
     * Uploads the file to the server.
     */
    submitFile() {
      if (this.file == null) {
        return;
      }

      console.log("Submitting file for upload...");
      let formData = new FormData();
      formData.append('file', this.file);

      axios.post(`api/upload`, formData, {
        headers: { 
          'Content-Type': 'multipart/form-data' 
        },
        timeout: 5000
      })
        .then(response => {
          console.log("File upload successful!");
          this.$refs.fileInput.value = "";
          console.log(response);
        }).catch(error => {
          console.log("File upload failed.");
          console.error(error);
        });
    }
  }
}
</script>

Handle file upload in Flask

Now we have a basic form to upload the file to the server with Axios, let's create an API endpoint that will actually upload the file to Azure Blob Storage.

Inside app/api/resources.py we'll add a route to handle this operation.

app/api/resources.py
"""
REST API Resource Routing
http://flask-restplus.readthedocs.io
"""


import io
from datetime import datetime
from flask import request, jsonify, send_file
from flask_restx import Resource
from azure.storage.blob import BlobServiceClient, ContainerClient
from .security import require_auth
from . import api_rest


AZURE_CONNECTION_STRING = "DefaultEndpointsProtocol=https;" + \
    "AccountName=vueflaskstorageaccount;" + \
    "AccountKey=m6Vegjl44F28CnuujeYI27kZblp7pQBRftsuDXGLUN0PkfuRxAkY3MqJogwu2FShclWFWHfD3n4hJYeQEmk3GQ==;" + \
    "EndpointSuffix=core.windows.net"


@api_rest.route('/upload')
class UploadFile(Resource):
    """ Uploads file to Azure Blob Storage """

    def post(self):
        f = request.files["file"]

        try:
            service_client = BlobServiceClient.from_connection_string(AZURE_CONNECTION_STRING)
            container_client = service_client.get_container_client("pdf-container")
            blob_client = container_client.get_blob_client(f.filename)
            blob_client.upload_blob(f)

            return jsonify(success=True)
        except:
            return jsonify(success=False)

After assigning the connection string from earlier to AZURE_CONNECTION_STRING (in production you don't want to hardcode this sensitive connection string, instead use an environment variable) we initialise a service client which gets the container and uploads the file inside of it.

The sample and introduction from Microsoft are useful for learning more about working with the Azure Blob Storage SDK for Python.

Listing all files in the container

Whilst in app/api/resources.py we're gonna add in two more routes, one to get all files in the container, and another to download a given file by name. This will allow us to list all files in the application and select one to download. Add these under the UploadFile class.

app/api/resources.py
@api_rest.route('/all-files')
class GetAllFiles(Resource):
    """ Gets all filenames in the Azure Blob Storage container """

    def get(self):
        container = ContainerClient.from_connection_string(
            conn_str=AZURE_CONNECTION_STRING, 
            container_name="pdf-container"
        )

        all_filenames = []
        blob_list = container.list_blobs()
        for blob in blob_list:
            all_filenames.append(blob.name)

        return {
            "filenames": all_filenames
        }


@api_rest.route('/download/<string:filename>')
class DownloadFile(Resource):
    """ Downloads a file from Azure Blob Storage by filename """


    def post(self, filename):
        service_client = BlobServiceClient.from_connection_string(AZURE_CONNECTION_STRING)
        container_client = service_client.get_container_client("pdf-container")
        blob_client = container_client.get_blob_client(filename)

        bytes_stream = io.BytesIO()

        blob_data = blob_client.download_blob()
        blob_data.readinto(bytes_stream)

        bytes_stream.seek(0)

        return send_file(bytes_stream,
                          attachment_filename=blob_data.name,
                          mimetype="application/pdf",
                          as_attachment=True)

Downloading a file

Now we have API endpoints to handle both returning the list of files in the container, and to actually download a file, let's make a very simple UI to do both. We'll repurpose src/views/Api.vue for this.

src/views/Api.vue
<template>
  <div class="all-files-container">
    <ul>
      <li v-for="filename in files" :key="filename.id">
        <a href="#" @click.prevent="downloadFile(filename)">{{ filename }}</a>
      </li>
    </ul>
  </div>
</template>


<script>
/* eslint-disable */
import axios from 'axios'

export default {
  name: 'all-files',
  data () {
    return {
      files: [],
    }
  },
  mounted () {
    this.getAllFiles();
  },
  methods: {
    getAllFiles () {
      axios.get(`api/all-files`)
        .then(response => {
          this.files = response.data.filenames;
        }).catch(error => {
          console.error(error);
        });
    },
    downloadFile(filename) {
      axios({
        url: `api/download/${filename}`,
        method: "POST",
        responseType: "blob",
      }).then(response => {
          const blob = new Blob([response.data], { 
            type: 'application/pdf'
          });
          const url = window.URL.createObjectURL(blob);
          const link = document.createElement("a");
          link.target = "_blank";
          link.href = url;
          link.download = filename;
          link.click();
          window.URL.revokeObjectURL(url);
        }).catch(error => {
          console.error(error);
        });
    }
  }
}
</script>

<style lang="scss">
</style>

What we learned

We now have a small but working application that can handle file uploads and downloads. We learned how to build an upload form in Vue.js and then configure and work with Axios and the Azure Blob Storage Python package. Let's take a look at a short video of the application in action! Let's upload three PDF files from my Downloads folder to Azure Blob Storage then download them via the app.

How to change video quality?
How to change video playback speed?

I hope you enjoyed this article and can put what you learned here into practice in your own projects. If you're interested in deploying a Vue Flask app be sure to check out the article Automated deployment of a Vue Flask app using Azure Pipelines.

If you enjoyed this article be sure to check out other articles on the site.