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.


Technologies used:

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

Backend Project Directory:




Frontend Project Directory:



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

@Service
public 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)
@RestController
public 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 Size
spring.servlet.multipart.max-request-size=215MB



Spring Boot Driver

@SpringBootApplication
public 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

From the browser call the endpoint http://localhost:3000/

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete