Upload to Azure Blob Storage using a Web Browser

Summary

This post demonstrates how to create a web application that uploads files to Azure Blob Storage. The web application is a plain node.js application which can be uploaded to Azure Container Apps or uploaded as a Web App.

Setup Storage Account

  • Create a new Storage Account.
  • Enable the following under the Resource Sharing (CORS) options in the storage account:

Allowed Origins: *
Allowed Methods: DELETE,GET,HEAD,MERGE,POST,OPTIONS,PATCH,PUT
Allowed Headers: *
Exposed Headers: *
Max Age: 86400

  • Generate a SAS URL from the Container. In the example below, I will select Object under the Allowed resource types.

Copy and store the SAS URL.

Setup Node.js

The Node.js project will have the following file structure:

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        29/04/2022     17:54                .parcel-cache
d-----        29/04/2022     17:54                dist
d-----        29/04/2022     17:54                node_modules
-a----        29/04/2022     17:46            739 index.html
-a----        29/04/2022     17:53           4012 index.js
-a----        29/04/2022     17:54         173597 package-lock.json
-a----        29/04/2022     17:54            506 package.json

Install Packages

  • Create a new directory called ‘BlobFileUpload’.
  • Run command:
npm init -y
npm install @azure/storage-blob
npm install parcel
  • Update the package.json file to:
{
  "name": "blobupload",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "parcel ./index.html"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@azure/storage-blob": "^12.9.0",
    "parcel": "^2.5.0"
  },
  "browserslist": [
    "last 1 Edge version",
    "last 1 Chrome version",
    "last 1 Firefox version",
    "last 1 safari version",
    "last 1 webkit version"
  ],
  "devDependencies": {
    "buffer": "^6.0.3"
  }
}

Create index.html & index.js

index.html

Create an index.html file in the root directory.

<!-- index.html -->
<!DOCTYPE html>
<html>

<body>
    <button id="create-container-button">Create container</button>
    <button id="select-button">Select and upload files</button>
    <input type="file" id="file-input" multiple style="display: none;" />
    <button id="list-button">List files</button>
    <button id="delete-button">Delete selected files</button>
    <button id="delete-container-button">Delete container</button>
    <p><b>Status:</b></p>
    <p id="status" style="height:160px; width: 593px; overflow: scroll;" />
    <p><b>Files:</b></p>
    <select id="file-list" multiple style="height:222px; width: 593px; overflow: scroll;" />
</body>

<script type="module" src="./index.js"></script>

</html>

index.js

Update the const blobSasUrl with the SAS URL you created earlier.

const { BlobServiceClient } = require("@azure/storage-blob");

const createContainerButton = document.getElementById("create-container-button");
const deleteContainerButton = document.getElementById("delete-container-button");
const selectButton = document.getElementById("select-button");
const fileInput = document.getElementById("file-input");
const listButton = document.getElementById("list-button");
const deleteButton = document.getElementById("delete-button");
const status = document.getElementById("status");
const fileList = document.getElementById("file-list");

const reportStatus = message => {
    status.innerHTML += `${message}<br/>`;
    status.scrollTop = status.scrollHeight;
}

// Update <placeholder> with your Blob service SAS URL string
const blobSasUrl = "<SAS URL>";

// Create a new BlobServiceClient
const blobServiceClient = new BlobServiceClient(blobSasUrl);

// Create a unique name for the container by 
// appending the current time to the file name
const containerName = "container" + new Date().getTime();

// Get a container client from the BlobServiceClient
const containerClient = blobServiceClient.getContainerClient(containerName);

// Create
const createContainer = async () => {
    try {
        reportStatus(`Creating container "${containerName}"...`);
        await containerClient.create();
        reportStatus(`Done.`);
    } catch (error) {
        reportStatus(error.message);
    }
};

const deleteContainer = async () => {
    try {
        reportStatus(`Deleting container "${containerName}"...`);
        await containerClient.delete();
        reportStatus(`Done.`);
    } catch (error) {
        reportStatus(error.message);
    }
};

createContainerButton.addEventListener("click", createContainer);
deleteContainerButton.addEventListener("click", deleteContainer);

// List
const listFiles = async () => {
    fileList.size = 0;
    fileList.innerHTML = "";
    try {
        reportStatus("Retrieving file list...");
        let iter = containerClient.listBlobsFlat();
        let blobItem = await iter.next();
        while (!blobItem.done) {
            fileList.size += 1;
            fileList.innerHTML += `<option>${blobItem.value.name}</option>`;
            blobItem = await iter.next();
        }
        if (fileList.size > 0) {
            reportStatus("Done.");
        } else {
            reportStatus("The container does not contain any files.");
        }
    } catch (error) {
        reportStatus(error.message);
    }
};

listButton.addEventListener("click", listFiles);

// Upload
const uploadFiles = async () => {
    try {
        reportStatus("Uploading files...");
        const promises = [];
        for (const file of fileInput.files) {
            const blockBlobClient = containerClient.getBlockBlobClient(file.name);
            promises.push(blockBlobClient.uploadBrowserData(file));
        }
        await Promise.all(promises);
        reportStatus("Done.");
        listFiles();
    }
    catch (error) {
        reportStatus(error.message);
    }
}

selectButton.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", uploadFiles);

// Delete
const deleteFiles = async () => {
    try {
        if (fileList.selectedOptions.length > 0) {
            reportStatus("Deleting files...");
            for (const option of fileList.selectedOptions) {
                await containerClient.deleteBlob(option.text);
            }
            reportStatus("Done.");
            listFiles();
        } else {
            reportStatus("No files selected.");
        }
    } catch (error) {
        reportStatus(error.message);
    }
};

deleteButton.addEventListener("click", deleteFiles);

Result

Run the following command to start the server.

npm start

The following page should run:

Test with the following actions:

  1. Create container.
  2. Select and Upload files.
  3. List files
  4. Delete selected files.
  5. Delete container.

Each action should result in a change to Azure Blob Storage.