Azure Blob Storage can be used in a variety of different ways including via the Azure Portal, the Azure CLI and using Azure Blob Storage client libraries that are supported in many different programming languages.
In this tutorial, we will use the Node.js Azure Blob Storage client library to create an App Service Web App written in Javascript to read and write blob files.
Please note this project is not intended to be used in production, it is only offered as a way to teach you how to use the Node.js blob storage client library. There is a security risk running this in production that anyone with access to your API can add new blobs to your storage account and rack up storage and bandwidth related charges.
Keep that in mind when following this tutorial.
Creating Azure Resources for an Azure Blob Storage Account
Login via the Azure CLI using the az login command
1
az login
Create a Resource Group using the az group create command
1
az group create -g cloudengineerskills-sa-rg -l eastus
Creating a Storage Account using the az storage account create command
What we will be building is an Node.js API written using the Express framework, that will use the Node.js Blob Storage Client Library to interact with an Azure Blob Storage Account performing the following types of operations.
{"name":"nodejs-blob-storage-client","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","start":"node index.js"},"author":"","license":"ISC","dependencies":{"@azure/identity":"^4.4.1","@azure/storage-blob":"^12.24.0","dotenv":"^16.4.5","express":"^4.19.2"}}
Creating the .env file in the root of the project directory
1
AZURE_STORAGE_ACCOUNT_NAME=cloudengineerskillssa
Implementing the Express API using the Node.js Blob Storage Client Library
constexpress=require("express");const{BlobServiceClient}=require("@azure/storage-blob");const{DefaultAzureCredential}=require("@azure/identity");require("dotenv").config();const{streamToText}=require("./util.js");constapp_name="Custom Node.js Express Blobs API";constapp=express();constport=8080;constblobServiceClient=newBlobServiceClient(`https://${process.env.AZURE_STORAGE_ACCOUNT_NAME}.blob.core.windows.net`,newDefaultAzureCredential());app.use(express.json());app.get("/",(req,res)=>{res.send(`Hello from the ${app_name}`);});// create a container in a storage accountapp.post("/containers",async (req,res)=>{try{const{name}=req.body;console.log(`Attempting creation of container with name ${name}`);constcontainerClient=blobServiceClient.getContainerClient(name);constcreateContainer=awaitcontainerClient.create();const{requestId,date,etag}=createContainer;res.send({name,requestId,date,etag});}catch (error){res.status(500).send("Unable to create the container");}});// list all containers in a storage account// https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-containers-list-javascriptapp.get("/containers",async (req,res)=>{try{console.log(`Attempting listing all containers in blob storage account`);letcontainers=[];forawait (constcontainerItemofblobServiceClient.listContainers()){const{name}=containerItem;containers.push({name});}res.send({containers});}catch (error){res.status(500).send("Unable to list containers");}});// upload a blob to a containerapp.post("/containers/:containerName/blobs",async (req,res)=>{try{const{name,text}=req.body;const{containerName}=req.params;console.log(`Attempting upload of blob with name ${name} and text '${text}' to container ${containerName}`);constcontainerClient=blobServiceClient.getContainerClient(containerName);constblockBlobClient=containerClient.getBlockBlobClient(name);// https://learn.microsoft.com/en-us/javascript/api/@azure/storage-blob/blockblobuploadoptions?view=azure-node-latestconstuploadBlobOptions={blobHTTPHeaders:{blobContentType:"Content-Type: text/plain"},metadata:{source:app_name,},};// upload options https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-upload-javascript#upload-data-to-a-block-blobconstuploadBlobResponse=awaitblockBlobClient.upload(text,text.length,uploadBlobOptions);const{requestId,date,etag}=uploadBlobResponse;res.send({name,requestId,date,etag});}catch (error){res.status(500).send("Unable to upload blob to container");}});// list all blobs in a containerapp.get("/containers/:containerName/blobs",async (req,res)=>{try{const{containerName}=req.params;console.log(`Attempting list all blobs within container ${containerName}`);constcontainerClient=blobServiceClient.getContainerClient(containerName);// https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-list-javascriptletblobs=[];forawait (constblobofcontainerClient.listBlobsFlat()){const{name,url}=blob;blobs.push({name,url});}res.send({blobs});}catch (error){res.status(500).send("Unable to list blobs in the container");}});// get blob contentsapp.get("/containers/:containerName/blobs/:blobName",async (req,res)=>{try{const{containerName,blobName}=req.params;console.log(`Attempting get blob ${blobName} contents blobs within container ${containerName}`);constcontainerClient=blobServiceClient.getContainerClient(containerName);constblockBlobClient=containerClient.getBlockBlobClient(blobName);constdownloadBlockBlobResponse=awaitblockBlobClient.download(0);constdata=awaitstreamToText(downloadBlockBlobResponse.readableStreamBody);res.send({data});}catch (error){res.status(500).send("Unable to get blob contents");}});// delete a blobapp.delete("/containers/:containerName/blobs/:blobName",async (req,res)=>{try{const{containerName,blobName}=req.params;console.log(`Attempting deletion of blob ${blobName} within container ${containerName}`);constcontainerClient=blobServiceClient.getContainerClient(containerName);constblockBlobClient=containerClient.getBlockBlobClient(blobName);constdeleteBlob=awaitblockBlobClient.delete();const{requestId}=deleteBlob;res.send({blobName,requestId});}catch (error){res.status(500).send("Unable to delete the blob");}});// delete a containerapp.delete("/containers/:containerName",async (req,res)=>{try{const{containerName}=req.params;console.log(`Attempting deletion of container with name ${containerName}`);constcontainerClient=blobServiceClient.getContainerClient(containerName);constdeleteContainer=awaitcontainerClient.delete();const{requestId}=deleteContainer;res.send({containerName,requestId});}catch (error){res.status(500).send("Unable to delete the container");}});app.listen(port,()=>{console.log(`${app_name} app listening on port ${port}`);});
Login using the Azure CLI with az login and select your account and subscription you wish to use.
1
az login
Next we will grant the Storage Blob Data Contributor Role to the signed in user enabling us to write blobs to the containers within the Azure Storage Account.
1
az ad signed-in-user show --query id -o tsv | az role assignment create --role "Storage Blob Data Contributor" --assignee "@-" --scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>"
You can check in the Azure Portal that this has been signed correctly under My Permissions.
Note that role changes can take up to 10 minutes to take effect.
Run the web application locally with
1
npm run start
You may run various curl command against the application running on http://localhost:8080 to test it is working.
az appservice plan create --resource-group cloudengineerskills-sa-rg --name NodeAppPlan --number-of-workers 1 --sku B1 --is-linux --location eastus
Create and Deploy the Web App
1
az webapp up --name CloudEngineerSkillsNodeApp --resource-group cloudengineerskills-sa-rg --plan NodeAppPlan --runtime "NODE:20-lts"
Configure App Settings
Configure the App Setting for the Storage Account
1
az webapp config appsettings set -g cloudengineerskills-sa-rg -n CloudEngineerSkillsNodeApp --settings AZURE_STORAGE_ACCOUNT_NAME=cloudengineerskillssa
Configure the App Setting to forward to port 8080 where the Express Server is listening
1
az webapp config appsettings set -g cloudengineerskills-sa-rg -n CloudEngineerSkillsNodeApp --settings WEBSITES_PORT=8080
Creating a System assigned managed identity
This will provide the App Service Web App CloudEngineerSkillsNodeApp a system assigned managed identity granting the access to the Storage Blob Data Contributor role on the Storage Account.
1
az webapp identity assign --resource-group cloudengineerskills-sa-rg --name CloudEngineerSkillsNodeApp
Replace the <managedIdentityId> below with the principalId output from the az webapp identity assign command, also replace the <subscription-id>, <resource-group-name>, and the <storage-account-name>
1
az role assignment create --assignee "<managedIdentityId>" --role "Storage Blob Data Contributor" --scope "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>"
Restart the Web App for the App Settings Changes to take effect
1
az webapp restart --name CloudEngineerSkillsNodeApp --resource-group cloudengineerskills-sa-rg
Interacting with a Storage Account via the App Service Web App