Azure Blob Storage + Spring Boot + React - File Upload, Download, and Delete Example
Hello everyone, Hope you are doing well, today we will learn how to upload, download, and delete the file with Azure Blob Storage, Spring Boot, and React. You could download the source code from our GitHub repository.
Technologies used
Backend:
- Spring Boot 2.7.1
- Java 17
Frontend:
- React 17.0.2
- Axios 0.27.2
- Bootstrap 4.6.0
- react-dropzone 11.3.4
Cloud Service:
- Azure Blob Storage
A little bit of Background
Azure Blob Storage
Azure Blob storage is Microsoft's object storage solution for the cloud. Blob storage is optimized for storing massive amounts of unstructured data. Unstructured data is data that doesn't adhere to a particular data model or definition, such as text or binary data.
Spring Boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run". More Info - https://spring.io/projects/spring-boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run".
More Info - https://spring.io/projects/spring-boot
React
React is a JavaScript library for building user interfaces.More Info - https://reactjs.org/docs/getting-started.html
React is a JavaScript library for building user interfaces.
More Info - https://reactjs.org/docs/getting-started.html
Axios
Axios is the promise-based HTTP client for the browser.More Info - https://axios-http.com/docs/intro
Axios is the promise-based HTTP client for the browser.
More Info - https://axios-http.com/docs/intro
react-dropzone
Simple React hook to create an HTML5-compliant drag'n'drop zone for files.More Info - https://react-dropzone.js.org/
Simple React hook to create an HTML5-compliant drag'n'drop zone for files.
More Info - https://react-dropzone.js.org/
Let's begin,
Sign in to Azure Portal and create a resource group
Sign in to Azure portal https://portal.azure.com/#home and find "Resource groups" like below.
Then, create a resource group like the one below.
Create an Azure Storage Account and blob container using Azure Portal
Sign in to Azure portal https://portal.azure.com/#home and search for "Storage accounts" like below.Create a storage account,Enter/Select Resource group, Storage account name, etc... Then click on the "Review + create" button.
You will be taken to a page like the below image,Then click on the "Create" button.
Now, You can see "Deployment is in progress" like the below image.
Once deployment is completed you can see the "Your deployment is complete" page like the below image.Then click on the "Connection String"
Sign in to Azure portal https://portal.azure.com/#home and search for "Storage accounts" like below.
Create a storage account,
Enter/Select Resource group, Storage account name, etc... Then click on the "Review + create" button.
Then click on the "Create" button.
Now, You can see "Deployment is in progress" like the below image.
Once deployment is completed you can see the "Your deployment is complete" page like the below image.
Then click on the "Connection String"
We will build two projects:
1. Backend: spring-azure-storage-blob2. Frontend: react-drag-drop-file-upload-download
1. Backend: spring-azure-storage-blob
2. Frontend: react-drag-drop-file-upload-download
Backend
Final Project Directory:
Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-azure-storage-blob</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-azure-storage-blob</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.13.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml
azure:
storage:
container:
name: <Container name>
connection:
string: <Connection string>
Azure Blob Configuration
package com.knf.dev.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
@Configuration
public class AzureBlobConfig {
@Value("${azure.storage.connection.string}")
private String connectionString;
@Value("${azure.storage.container.name}")
private String containerName;
@Bean
public BlobServiceClient clobServiceClient() {
BlobServiceClient blobServiceClient =
new BlobServiceClientBuilder()
.connectionString(connectionString)
.buildClient();
return blobServiceClient;
}
@Bean
public BlobContainerClient blobContainerClient() {
BlobContainerClient blobContainerClient =
clobServiceClient()
.getBlobContainerClient(containerName);
return blobContainerClient;
}
}
Azure Blob Service
package com.knf.dev.demo.service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.azure.core.http.rest.PagedIterable;
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.models.BlobItem;
@Component
public class AzureBlobService {
@Autowired
BlobServiceClient blobServiceClient;
@Autowired
BlobContainerClient blobContainerClient;
public String upload(MultipartFile multipartFile)
throws IOException {
// Todo UUID
BlobClient blob = blobContainerClient
.getBlobClient(multipartFile.getOriginalFilename());
blob.upload(multipartFile.getInputStream(),
multipartFile.getSize(), true);
return multipartFile.getOriginalFilename();
}
public byte[] getFile(String fileName)
throws URISyntaxException {
BlobClient blob = blobContainerClient.getBlobClient(fileName);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
blob.download(outputStream);
final byte[] bytes = outputStream.toByteArray();
return bytes;
}
public List<String> listBlobs() {
PagedIterable<BlobItem> items = blobContainerClient.listBlobs();
List<String> names = new ArrayList<String>();
for (BlobItem item : items) {
names.add(item.getName());
}
return names;
}
public Boolean deleteBlob(String blobName) {
BlobClient blob = blobContainerClient.getBlobClient(blobName);
blob.delete();
return true;
}
}
Create a Controller
package com.knf.dev.demo.controller;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.knf.dev.demo.service.AzureBlobService;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
public class AzureController {
@Autowired
private AzureBlobService azureBlobService;
@PostMapping("/upload")
public ResponseEntity<String> upload
(@RequestParam("file") MultipartFile file)
throws IOException {
String fileName = azureBlobService.upload(file);
return ResponseEntity.ok(fileName);
}
@GetMapping("/files")
public ResponseEntity<List<String>> getAllBlobs() {
List<String> items = azureBlobService.listBlobs();
return ResponseEntity.ok(items);
}
@DeleteMapping("/delete/{name}")
public ResponseEntity<Boolean> delete
(@PathVariable("name") String name) {
azureBlobService.deleteBlob(name);
return ResponseEntity.ok().build();
}
@GetMapping(path = "/download/{name}")
public ResponseEntity<Resource> getFile
(@PathVariable("name") String name)
throws URISyntaxException {
ByteArrayResource resource =
new ByteArrayResource(azureBlobService
.getFile(name));
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" +
name + "\"");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.headers(headers).body(resource);
}
}
Spring Boot Main Driver
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package.json
{ "name": "react-drag-drop-file-upload-download", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "axios": "^0.27.2", "bootstrap": "^4.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-dropzone": "^11.3.4", "react-scripts": "4.0.3", "web-vitals": "^1.0.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }}
Common.js
import axios from "axios";
export default axios.create({ baseURL: "http://localhost:8080", headers: { "Content-type": "application/json" }});
/services/UploadFilesService.js
import http from "../Common";
class UploadFilesService { upload(file, onUploadProgress) { let formData = new FormData();
formData.append("file", file);
return http.post("/upload", formData, { headers: { "Content-Type": "multipart/form-data", }, onUploadProgress, }); }
getFiles() { return http.get("/files"); }
deleteFile(name){ return http.delete("/delete/"+name);}}
export default new UploadFilesService();
/components/UploadFilesComponent.js
import React, { Component } from "react";import Dropzone from "react-dropzone";import UploadService from "../services/UploadFilesService";
export default class UploadFilesComponent extends Component {
constructor(props) { super(props); this.upload = this.upload.bind(this); this.onDrop = this.onDrop.bind(this); this.deleteFile = this.deleteFile.bind(this);
this.state = { selectedFiles: undefined, currentFile: undefined, progress: 0, message: "", fileInfos: [], }; }
deleteFile(name){ UploadService.deleteFile(name).then( res => { this. componentDidMount(); });}
componentDidMount() { UploadService.getFiles().then((response) => { this.setState({ fileInfos: response.data, }); }); }
upload() { let currentFile = this.state.selectedFiles[0];
this.setState({ progress: 0, currentFile: currentFile, });
UploadService.upload(currentFile, (event) => { this.setState({ progress: Math. round((100 * event.loaded) / event.total), }); }) .then((response) => { this.setState({ message: response.data.message, }); return UploadService.getFiles(); }) .then((files) => { this.setState({ fileInfos: files.data, }); }) .catch(() => { this.setState({ progress: 0, message: "Could not upload the file!", currentFile: undefined, }); });
this.setState({ selectedFiles: undefined, }); }
onDrop(files) { if (files.length > 0) { this.setState({ selectedFiles: files }); } }
render() { const { selectedFiles, currentFile, progress, message, fileInfos } = this.state;
return ( <div> <Dropzone onDrop={this.onDrop} multiple={false}> {({ getRootProps, getInputProps }) => ( <section> <div {...getRootProps({ className: "dropzone" })}> <input {...getInputProps()} /> {selectedFiles && selectedFiles[0].name ? ( <div className="selected-file"> {selectedFiles && selectedFiles[0].name} </div> ) : ( <h4>Drag and drop file here, or click to select file</h4> )} </div> <aside className="selected-file-wrapper"> <button className="btn btn-secondary btn-lg btn-block" disabled={!selectedFiles} onClick={this.upload}> Upload </button> </aside> </section> )} </Dropzone> <br></br> {currentFile && ( <div className="progress mb-3"> <div className ="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuenow={progress} aria-valuemin="0" aria-valuemax="100" style={{ width: progress + "%" }} > {progress}% </div> </div> )} <div className="alert alert-light" role="alert"> {message} </div>
{fileInfos.length > 0 && ( <div className="card"> <table class="table"> <thead> <tr> <th scope="col">File Name</th> <th scope="col">Action</th> <th scope="col">Action</th> </tr> </thead> {fileInfos.map((file) => ( <> <tbody> <tr> <td>{file}</td> <td><a href={`http://localhost:8080/download/${file}`} class="btn btn-success">Download</a></td> <td><button onClick={ () => this.deleteFile(file)} type="button" class="btn btn-danger"> Delete</button></td> </tr> </tbody> </> ))} </table> </div> )} </div> ); }}
App.js
import React from "react";import "./App.css";import "bootstrap/dist/css/bootstrap.min.css";import UploadFiles from "./components/UploadFilesComponent";
function App() { return (
<div> <nav class="navbar navbar-dark bg-dark"> <div class="btn-group mx-auto"> <h4 class="text-white">Azure Blob Storage + Spring Boot + React - File Upload , Download, & Delete</h4> </div> </nav><br></br> <div class="container"> <UploadFiles /> </div></div> );}
export default App;
App.css
.dropzone { text-align: center; padding: 40px; border: 4px dashed #eeeeee; background-color: #fafafa; color: #bdbdbd; cursor: pointer; margin-bottom: 20px;}
.selected-file-wrapper { text-align: center;}
.selected-file { color: #000; font-weight: bold;}
index.js
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import * as serviceWorker from './serviceWorker';
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root'));
serviceWorker.unregister();
index.css
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}
code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;}
/public/index.html
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body></html>
Download the complete source code - click here
Local Setup and Run the application
Step1: Download or clone the source code from GitHub to the local machine - Click here
Backend
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Frontend
Step 4: npm install
Step 5: npm start