Upload an image to an Azure Storage blob

Use a client-side React app to upload an image file to an Azure Storage blob using an Azure Storage @azure/storage-blob npm package.

The TypeScript programming work is done for you, this tutorial focuses on using the local and remote Azure environments successfully from inside Visual Studio Code with Azure extensions.

Application architecture and functionality

This article includes several top Azure tasks for JavaScript developers:

  • Run a React/TypeScript app locally with Visual Studio Code
  • Create an Azure Storage Blob resource and configure for file uploads
    • Configure CORS
    • Create Shared access signatures (SAS) token
  • Configure code for Azure SDK client library to use SAS token to authenticate to service

The sample React app, available on GitHub, consists of the following elements:

Simple React app connected to Azure Storage blobs.

1. Set up development environment

2. Fork and clone the sample application

  1. Open this GitHub sample URL in a web browser:

    https://github.com/Azure-Samples/js-e2e-browser-file-upload-storage-blob
    
  2. Select Fork to create your own fork of this sample project. Your own GitHub fork is necessary to deploy this sample to Azure as a static web app.

  3. Select the Code button, then copy the Clone URL.

  4. In a bash terminal, clone your forked repository, replacing REPLACE-WITH-YOUR-ACCOUNT-NAME with your GitHub account name:

    git clone https://github.com/REPLACE-WITH-YOUR-ACCOUNT-NAME/js-e2e-browser-file-upload-storage-blob
    
    
  5. Change into the new directory and open Visual Studio Code.

    cd js-e2e-browser-file-upload-storage-blob && code .
    

3. Install dependencies and run local project

  1. In Visual Studio Code, open an integrated bash terminal, Ctrl + Shift + `, and run the following command to install the sample's dependencies.

    npm install
    
  2. In the same terminal window, run the command to run the web app.

    npm start
    
  3. Open a web browser and use the following url to view the web app on your local computer.

    http://localhost:3000/
    

    If you see the simple web app in your browser with the text that the Storage isn't configured, you have succeeded with this section of the tutorial.

    Simple Node.js app connected to MongoDB database.

  4. Stop the code with Ctrl + C in the Visual Studio Code terminal.

4. Create Storage resource with Visual Studio extension

  1. Navigate to the Azure Storage extension. Right-click on the subscription then select Create Storage Account....

    Navigate to the Azure Storage extension. Right-click on the subscription then select `Create Storage Account...`.

  2. Follow the prompts using the following table to understand how to create your Storage resource.

    Property Value
    Enter a globally unique name for the new web app. Enter a value such as fileuploaddemo, for your Storage resource name.

    This unique name is your resource name used in the next section. Use only characters and numbers, up to 24 in length. You need this account name to use later.
  3. When the app creation process is complete, a notification appears with information about the new resource.

    When the app creation process is complete, a notification appears with information about the new resource.

5. Generate your shared access signature (SAS) token

Generate the SAS token before configuring CORS.

  1. In the Visual Studio Code extension for Storage, right-click the resource then select Open in Portal. This opens the Azure portal to your exact Storage resource.

  2. In the Security + networking section, select Shared access signature.

  3. Configure the SAS token with the following settings.

    Property Value
    Allowed services Blob
    Allowed resource types Service, Container, Object
    Allowed permissions Read, write, delete, list, add, create
    Blob versioning permissions Checked
    Allow blob index permissions Read/Write and Filter should be checked
    Start and expiry date/time Accept the start date/time and set the end date time 24 hours in the future. Your SAS token is only good for 24 hours.
    HTTPS only Selected
    Preferred routing tier Basic
    Signing Key key1 selected

    Configure the SAS token as show in the image. The settings are explained below the image.

  4. Select Generate SAS and connection string.

  5. Immediately copy the SAS token. You won't be able to list this token so if you don't have it copied, you will need to generate a new SAS token.

6. Set Storage values in .env file

The SAS token is used when queries are made to your cloud-based resource.

  1. Create a file name .env at the root of the project.

  2. Add two required variables with their storage values:

    REACT_APP_STORAGESASTOKEN=
    REACT_APP_STORAGERESOURCENAME=
    

    React builds the static files with these variables.

  3. If the token begins with a question mark, remove the ?. The code file provides the ? for you so you don't need it in the token.

7. Configure CORS for Azure Storage resource

Configure CORS for your resource so the client-side React code can access your storage account.

  1. While still in the Azure portals, in the Settings section, select Resource sharing (CORS).

  2. Configure the Blob service CORS as show in the image. The settings are explained below the image.

    Property Value
    Allowed origins *
    Allowed methods All except patch.
    Allowed headers *
    Exposed headers *
    Max age 86400

    Configure CORS as show in the image. The settings are explained below the image.

  3. Select Save above the settings to save them to the resource. The code doesn't require any changes to work with these CORS settings.

8. Run project locally to verify connection to Storage account

Your SAS token and storage account name are set in the src/azure-storage-blob.ts file, so you are ready to run the application.

  1. If the app isn't running, start it again:

    npm start
    
  2. Open the following URL in a browser:

    http://localhost:3000

    The React website connected to Azure Storage blobs should display with a file selection button and a file upload button.

  3. Select an image from the images folder to upload then select the Upload! button.

  4. The React front-end client code calls into the ./src/azure-storage-blob.ts to authenticate to Azure, then create a Storage Container (if it doesn't already exist), then uploads the file to that container.

9. Deploy static web app to Azure

  1. In Visual Studio Code, select the Azure explorer.

  2. If you see a pop-up window asking you to commit your changes, don't do this. The sample should be ready to deploy without changes.

    To roll back the changes, in Visual Studio Code, select the Source Control icon in the activity bar. Then select each changed file in the Changes list, and select the Discard changes icon.

  3. Right-click on the subscription name, and then select Create Static Web App (Advanced).

  4. Follow the prompts to provide the following information:

    Prompt Enter
    Enter the name for the new static web app. Create a unique name for your resource. For example, you can prepend your name to the repository name, such as upload-file-to-storage.
    Select a resource group for new resources. Use the resource group that you created for your storage resource.
    Select a SKU Select the free SKU for this tutorial. If you already have a free Static Web App resource used, select the next pricing tier.
    Choose build preset to configure default project structure. Select React.
    Select the location of your application code / - This indicates the package.json file is at the root of the repository.
    Select the location of your Azure Functions code Accept the default value. While this sample doesn't use an API, you can add one later.
    Enter the path of your build output... build

    This is the path from your app to your static (generated) files.
    Select a location for new resources. Select a region close to you.
  5. When the process is complete, a notification pop-up displays. Select View/Edit Workflow.

    Partial screenshot of Visual Studio Code notification pop-up with View/Edit Workflow button highlighted.

10. Add Azure Storage secrets to GitHub secrets

  1. In a web browser, return to your GitHub fork of the sample project to add the two secrets and their values:

    https://github.com/YOUR-GITHUB-ACCOUNT/js-e2e-browser-file-upload-storage-blob/settings/secrets/actions
    

    Screenshot of a web browser displaying https://github.com, on the Settings -> Secrets page, with the New repository secret button highlighted.

11. Configure static web app to connect to storage resource

Edit the GitHub workflow and secrets to connect to Azure Storage.

  1. In Visual Studio Code, open the .github/workflows workflow YAML file and add the two storage environment variables after the with section to the build_and_deploy_job.

    jobs:
      build_and_deploy_job:
        if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
        runs-on: ubuntu-latest
        name: Build and Deploy Job
        steps:
          - uses: actions/checkout@v2
            with:
              submodules: true
          - name: Build And Deploy
            id: builddeploy
            uses: Azure/static-web-apps-deploy@v1
            with:
              azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_123456 }}
              repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
              action: "upload"
              ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
              # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
              app_location: "/" # App source code path
              api_location: "api" # Api source code path - optional
              output_location: "build" # Built app content directory - optional
              ###### End of Repository/Build Configurations ######
            env: # Add environment variables here
                REACT_APP_STORAGESASTOKEN: ${{secrets.REACT_APP_STORAGESASTOKEN}}
                REACT_APP_STORAGERESOURCENAME: ${{secrets.REACT_APP_STORAGERESOURCENAME}}
    

    This pulls in the secrets to the build process.

  2. In Visual Studio Code, select Source Control, Ctrl + Shift + G, then select the addition icon to add the changed *.yml file.

  3. Enter a comment for your commit such as Adding Storage secrets.

  4. Push to your GitHub fork by selecting the Synchronize changes icon on the status bar.

    Partial screenshot of Visual Studio Code status bar.

  5. In the pop-up window to confirm if you want to push and pull from your remote repository, select OK.

    If you get an error at this step, checkout your git remote to make sure you cloned your fork: git remote -v.

  6. This push triggers a new build and deploy for your static web app.

12. Verify build and deploy job completes

  1. In a web browser, return to your GitHub fork of the sample project.

  2. Select Actions, then select the Azure Static Web Apps CI/CD action.

  3. Select the Build and Deploy Job to watch the process complete.

    Screenshot of web browser showing GitHub action success

13. Use the Azure-deployed static web app

  1. In Visual Studio Code, right-click your Static web app from the Azure explorer and select

    Partial screenshot selecting Browse Site from the Azure Static web site.

  2. In the new web browser window, choose a file and upload the file.

Troubleshoot local connection to Storage account

If you received an error or your file doesn't upload to the container, check the following:

  • Recreate your SAS token, making sure that your token is created at the Storage resource level and not the container level. Copy the new token into the code at the correct location.
  • Check that the token string you copied into the code doesn't contain the ? (question mark) at the beginning of the string.
  • Verify your CORS setting for your Storage resource.

Upload button functionality

The src/App.tsx TypeScript file is provided as part of that app creation with create-react-app. The file has been modified to provide the file selection button and the upload button and the supporting code to provide that functionality.

The code connecting to the Azure Blob Storage code is highlighted. The call to uploadFileToBlob returns all blobs (files) in the container as a flat list. That list is displayed with the DisplayImagesFromContainer function.

// ./src/App.tsx

import React, { useState } from 'react';
import Path from 'path';
import uploadFileToBlob, { isStorageConfigured } from './azure-storage-blob';

const storageConfigured = isStorageConfigured();

const App = (): JSX.Element => {
  // all blobs in container
  const [blobList, setBlobList] = useState<string[]>([]);

  // current file to upload into container
  const [fileSelected, setFileSelected] = useState(null);

  // UI/form management
  const [uploading, setUploading] = useState(false);
  const [inputKey, setInputKey] = useState(Math.random().toString(36));

  const onFileChange = (event: any) => {
    // capture file into state
    setFileSelected(event.target.files[0]);
  };

  const onFileUpload = async () => {
    // prepare UI
    setUploading(true);

    // *** UPLOAD TO AZURE STORAGE ***
    const blobsInContainer: string[] = await uploadFileToBlob(fileSelected);

    // prepare UI for results
    setBlobList(blobsInContainer);

    // reset state/form
    setFileSelected(null);
    setUploading(false);
    setInputKey(Math.random().toString(36));
  };

  // display form
  const DisplayForm = () => (
    <div>
      <input type="file" onChange={onFileChange} key={inputKey || ''} />
      <button type="submit" onClick={onFileUpload}>
        Upload!
          </button>
    </div>
  )

  // display file name and image
  const DisplayImagesFromContainer = () => (
    <div>
      <h2>Container items</h2>
      <ul>
        {blobList.map((item) => {
          return (
            <li key={item}>
              <div>
                {Path.basename(item)}
                <br />
                <img src={item} alt={item} height="200" />
              </div>
            </li>
          );
        })}
      </ul>
    </div>
  );

  return (
    <div>
      <h1>Upload file to Azure Blob Storage</h1>
      {storageConfigured && !uploading && DisplayForm()}
      {storageConfigured && uploading && <div>Uploading</div>}
      <hr />
      {storageConfigured && blobList.length > 0 && DisplayImagesFromContainer()}
      {!storageConfigured && <div>Storage is not configured.</div>}
    </div>
  );
};

export default App;

Upload file to Azure Storage blob with Azure SDK client library

The code to upload the file to the Azure Storage is framework-agnostic. As the code is built for a tutorial, choices were made for simplicity and comprehension. These choices are explained; you should review your own project for intentional use, security, and efficiency.

The sample creates and uses a publicly accessible container and files. If you want to secure your files in your own project, you have many layers where you can control that from requiring overall authentication to your resource to very specific permissions on each blob object.

Dependencies and variables

The azure-storage-blob.ts TypeScript file loads the dependencies, and pulls in the required variables by either environment variables or hard-coded strings.

Variable Description
sasToken The SAS token created with the Azure portal is prepended with a ?. Remove it before setting it in your sasToken variable.
container The name of the container in Blob storage. You can think of this as equivalent to a folder or directory for a file system.
storageAccountName Your resource name.
// THIS IS SAMPLE CODE ONLY - NOT MEANT FOR PRODUCTION USE
import { BlobServiceClient, ContainerClient} from '@azure/storage-blob';

const containerName = `tutorial-container`;
const sasToken = process.env.REACT_APP_STORAGESASTOKEN;
const storageAccountName = process.env.REACT_APP_STORAGERESOURCENAME;

Create Storage client and manage steps

The uploadFileToBlob function is the main function of the file. It creates the client object for the Storage service, then creates the client to the container object, uploads the file, then gets a list of all the blobs in the container.

const uploadFileToBlob = async (file: File | null): Promise<string[]> => {
  if (!file) return [];

  // get BlobService = notice `?` is pulled out of sasToken - if created in Azure portal
  const blobService = new BlobServiceClient(
    `https://${storageAccountName}.blob.core.windows.net/?${sasToken}`
  );

  // get Container - full public read access
  const containerClient: ContainerClient = blobService.getContainerClient(containerName);
  await containerClient.createIfNotExists({
    access: 'container',
  });

  // upload file
  await createBlobInContainer(containerClient, file);

  // get list of blobs in container
  return getBlobsInContainer(containerClient);
};

Upload file to blob

The createBlobInContainer function uploads the file to the container, using the BlockBlobClient class, uploadData method. The content type must be sent with the request if you intend to use browser functionality, which depends on the file type, such as displaying a picture.

const createBlobInContainer = async (containerClient: ContainerClient, file: File) => {
  
  // create blobClient for container
  const blobClient = containerClient.getBlockBlobClient(file.name);

  // set mimetype as determined from browser with file upload control
  const options = { blobHTTPHeaders: { blobContentType: file.type } };

  // upload file
  await blobClient.uploadData(file, options);
}

Get list of blobs

The getBlobsInContainer function gets a list of URLs, using the ContainerClient class, listBlobsFlat method, for the blobs in the container. The URLs are constructed to be used as the src of an image display in HTML: <img src={item} alt={item} height="200" />.

// return list of blobs in container to display
const getBlobsInContainer = async (containerClient: ContainerClient) => {
  const returnedBlobUrls: string[] = [];

  // get list of blobs in container
  // eslint-disable-next-line
  for await (const blob of containerClient.listBlobsFlat()) {
    // if image is public, just construct URL
    returnedBlobUrls.push(
      `https://${storageAccountName}.blob.core.windows.net/${containerName}/${blob.name}`
    );
  }

  return returnedBlobUrls;
}

Clean up resources

In Visual Studio Code, use the Azure explorer for Resource Groups, right-click on the your resource group then select Delete.

This deletes all resources in the group, including your Storage and Static Web app resources.

Next steps

If you would like to continue with this app, learn how to deploy the app to Azure for hosting with one of the following choices: