Spring Boot + React - Drag and Drop File Upload & Download Example
Hello everyone, today we will learn how to (drag and drop) upload and download the file with Spring Boot and React. You could download the source code from our GitHub repository.
Backend:
- Spring Boot 2.7.0
- Java 17
Frontend:
- React 17.0.2
- Axios 0.27.2
- Bootstrap 4.6.0
- react-dropzone 11.3.4
We will build two projects:
1. Backend: springboot-fileupload-filedownload
2. Frontend: react-drag-drop-file-upload-download
Project 1: springboot-file-upload-download
pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.0</version> <!-- lookup parent from repository --> </parent> <groupId>com.knowledgefactory</groupId> <artifactId>springboot-file-upload-download</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>springboot-file-upload-download</name>
<properties> <java.version>17</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- spring mvc, rest --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <description>springboot-file-upload-download</description></project>
UploadDownloadService.java
@Servicepublic class UploadDownloadService { private static final String path = "/home/user/Desktop/files";
public List<String> uploadFile(MultipartFile file) throws Exception {
// Save file on system if (!file.getOriginalFilename().isEmpty()) {
BufferedOutputStream outputStream = new BufferedOutputStream( new FileOutputStream(new File(path, file.getOriginalFilename())));
outputStream.write(file.getBytes()); outputStream.flush(); outputStream.close();
} else { throw new Exception(); }
List<String> list = new ArrayList<String>(); File files = new File(path); String[] fileList = ((File) files).list(); for (String name : fileList) { list.add(name); }
return list;
}
public List<String> getListofFiles() throws Exception {
List<String> list = new ArrayList<String>(); File files = new File(path); String[] fileList = ((File) files).list(); for (String name : fileList) { list.add(name); }
return list;
}}
FileController.java
@CrossOrigin(origins = "*", maxAge = 3600)@RestControllerpublic class FileController {
private static final String path = "/home/user/Desktop/files/";
@Autowired UploadDownloadService service;
@PostMapping("/upload") public ResponseEntity<List<String>> fileUpload (@RequestParam("file") MultipartFile file) throws Exception {
return new ResponseEntity<>(service.uploadFile(file), HttpStatus.OK);
}
@GetMapping(path = "/download/{name}") public ResponseEntity<Resource> download (@PathVariable("name") String name) throws IOException {
File file = new File(path + name); Path path = Paths.get(file.getAbsolutePath()); ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
return ResponseEntity.ok().headers(this.headers(name)) .contentLength(file.length()) .contentType(MediaType .parseMediaType("application/octet-stream")) .body(resource); }
@GetMapping("/files") public ResponseEntity<List<String>> getListOfFiles() throws Exception {
return new ResponseEntity<>(service.getListofFiles(), HttpStatus.OK);
}
private HttpHeaders headers(String name) {
HttpHeaders header = new HttpHeaders(); header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + name); header.add("Cache-Control", "no-cache, no-store, must-revalidate"); header.add("Pragma", "no-cache"); header.add("Expires", "0"); return header;
}}
application.properties
spring.servlet.multipart.enabled=true# Threshold after which files are written to disk.spring.servlet.multipart.file-size-threshold=2KB# Max file size.spring.servlet.multipart.max-file-size=200MB# Max Request Sizespring.servlet.multipart.max-request-size=215MB
Spring Boot Driver
@SpringBootApplicationpublic class KnowledgefactorydemoApplication {
public static void main(String[] args) {
SpringApplication .run(KnowledgefactorydemoApplication.class, args); }}
Project 2: react-drag-drop-file-upload-download
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"); }}
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.state = { selectedFiles: undefined, currentFile: undefined, progress: 0, message: "", fileInfos: [], }; }
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"> <div className="card-header">Download the file</div> <ul className="list-group list-group-flush"> {fileInfos.map((file) => ( <a href={`http://localhost:8080/download/${file}`} class="list-group-item list-group-item-action "> <li>{file}</li></a> ))} </ul> </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"> <h2 class="text-white">Spring Boot + React Drag and Drop File Upload & Download</h2> </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;}
Download the complete source code - click here
Local Setup and Run the application
Step1: Download or clone the source code from GitHub to a 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