In this tutorial, we will learn how to upload images or files from a React Js app with Spring MVC Rest. We will have a React app with and without Axios integrated with it to push selected files in the browser to the server via REST. In the client-side, we will be using HTML5 FormData and in the server, we will be using Multipart file to accept those uploaded files. To ease our Spring configuration, we will be using Spring Boot. We will have examples to upload a single file as well as multiple files from React app.
At the end, we will also take a look into adding file validations in the client-side as well as in the server such as the max size of the file, file format, etc. Apart from this, we will also look into methods to download those uploaded files in Spring.
React Js App Setup
We will use CLI tool called create-react-app for creating react app which creates a sample app using react that can be used as a starting point to create a full-fledged react app. It has a dev server bundled by default for development. To setup react from scratch, you can follow this article.
Traverse to the folder location where you want to generate the project and execute below commands:
npx create-react-app react-js-file-upload cd my-app npm start
For the sake of this example, we will have our file upload implementations in App.js itself.
Let us add bootstrap dependencies for some styling in index.html
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css"> <script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
Spring Boot REST Setup
Head over to start.spring.io to generate your spring boot project. We require spring web for this project. Below is the maven dependecy.
pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
React Js File Upload Without Axios
Let us first add our jsx implementation for file upload.
One thing to notice here is the onChange()
event which will trigger the onFileChangeHandler
function which we will be implementing next.
render(){ return( <div className="container"> <div className="row"> <div className="col-md-6"> <div className="form-group files color"> <label>Upload Your File </label> <input type="file" className="form-control" name="file" onChange={this.onFileChangeHandler}/> </div> </div> </div> </div> ) }
Below is the implementation of onFileChangeHandler
. We will be using FormData interface to push our file and metadata if any.
As we are allowing to select a single file, e.target.files will have only one element in it.
We are using fetch API for now. In the below section we will be using Axios instead.
onFileChangeHandler = (e) => { e.preventDefault(); this.setState({ selectedFile: e.target.files[0] }); const formData = new FormData(); formData.append('file', this.state.selectedFile); fetch('http://localhost:8080/upload', { method: 'post', body: formData }).then(res => { if(res.ok) { console.log(res.data); alert("File uploaded successfully.") } }); };
Here, in the REST call, we are not setting the Content-Type as multipart/form-data
. The browser will do it for us along with setting up the multipart boundary. Else, we will get the error as org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
at the server-side.
Spring Boot REST Multipart File Implementation
Let us create our controller to expose the REST endpoint that will accept the file as a Multipart file.
As we are sending the file with Formdata interface, the same can be extracted in spring as a request param and the key used in the formData to append the file is file
, we are using the same key at server side.
Below API is a POST mapping that accepts MULTIPART_FORM_DATA_VALUE.
FileController.java@CrossOrigin("*") @RestController public class FileController { private static final Logger logger = LoggerFactory.getLogger(FileController.class); @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadFile(@RequestParam MultipartFile file) { logger.info(String.format("File name '%s' uploaded successfully.", file.getOriginalFilename())); return ResponseEntity.ok().build(); } }
In real-time, either you can save it to DB or in a local file system and in the response, you can send a custom URL from where the user can download the uploaded image. To save this image in DB, you can follow this article.
React Js File Upload With Axios
Now, let us add Axios in our React app first with NPM command line.
npm add axios
Now, the above method will be changed as below:
onFileChangeHandler = (e) => { e.preventDefault(); this.setState({ selectedFile: e.target.files[0] }); const formData = new FormData(); formData.append('file', this.state.selectedFile); ApiService.upload(formData) .then(res => { console.log(res.data); alert("File uploaded successfully.") }) };
Below is the service implementation with Axios.
import axios from 'axios'; class ApiService { upload(data) { return axios.post("http://localhost:8080/upload", data); } } export default new ApiService();
React Js Multiple File Upload
To upload multiple files, we need to change our html to allow multiple file selection.
<input type="file" className="form-control" name="file" multiple onChange={this.onFileChangeHandler}/>
Now, the onFileChangeHandler()
will be modified as:
onFileChangeHandler = (e) => { const formData = new FormData(); for(let i = 0; i< e.target.files.length; i++) { formData.append('file', e.target.files[i]) } ApiService.upload(formData) .then(res => { console.log(res.data); alert("File uploaded successfully.") }) };
These changes need to be accomodatd in the REST controller too. Now, it accepts an array of multipart file.
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity uploadFile(@RequestParam MultipartFile[] files) { for (int i = 0; i < files.length; i++) { logger.info(String.format("File name '%s' uploaded successfully.", files[i].getOriginalFilename())); } return ResponseEntity.ok().build(); }
Sending Extra Metadata with File Upload to Server
To send some extra metatadata along with file upload, you can append it in the exisitng FormData as below:
formData.append("extra", "estra metadata");
Now, the same can be retrieved in the server-side as
public ResponseEntity uploadFile(@RequestParam MultipartFile[] files, @RequestParam String extra) { }
File Upload Validations
The different validations that can be added are to restrict user to upload some specific number of files at a time. This can be done at the client side by adding condition at e.target.files.length
. Similarly, we can add validation of the file size at the client-side with file size API in Javascript.
Now, coming to server side validations, we can restrict the file size with a bean definition. By default, the permitted size for tomcat is 1048576 bytes and this can be overriden with below configuration. If the file size exceeds, below error is thrown:
org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.
@Bean(name = "multipartResolver") public CommonsMultipartResolver multipartResolver() { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(-1); return multipartResolver; }
Here, -1 means no restriction at all.
We need to add below maven dependency to avoid exception while deploying the app after adding above defined app. The error would be similar to
java.lang.NoClassDefFoundError: org/apache/commons/fileupload/disk/DiskFileItemFactory
REST API File Download
In this example, we are not persistently saving the file and hence I will demonstrate it in the next article. But if you are saving the file persistently then below is the controller method implementation to download it. The only difference is the content-type.
@RequestMapping("/download") public ResponseEntitydownloadFile1(@RequestParam String fileName) throws IOException { File file = new File(fileName); InputStreamResource resource = new InputStreamResource(new FileInputStream(file)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName()) .contentType(MediaType.APPLICATION_OCTET_STREAM) .contentLength(file.length()) .body(resource); }
Conclusion
In this article, we learned about uploading and downloading files and images with React Js and Spring app.