Spring Boot React JS File Upload and Download Example

In this tutorial, we will create a full-stack application using Spring Boot 3 for the backend and React 18 (using functional components and hooks) for the frontend. We will implement both file upload and download functionalities. The tutorial will cover setting up the project, creating a Spring Boot REST API for file upload and download, and building a React application for the same. We will also use Bootstrap for styling.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • Node.js and npm installed
  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Step 1: Setting Up the Spring Boot Project

1.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3
    • Group: com.example
    • Artifact: spring-boot-react-file
    • Name: spring-boot-react-file
    • Description: Full Stack Application with Spring Boot and React for File Upload and Download
    • Package Name: com.example.springbootreactfile
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Web
      • Spring Boot DevTools
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

1.2 Project Structure

After importing the project, you will see the following structure in your IDE:

spring-boot-react-file
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── springbootreactfile
│   │   │               ├── SpringBootReactFileApplication.java
│   │   │               ├── controller
│   │   │               └── service
│   ├── main
│   │   └── resources
│   │       ├── application.properties
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── springbootreactfile
│                       └── SpringBootReactFileApplicationTests.java
└── pom.xml

Step 2: Creating the Backend

2.1 Create the File Service

This service will handle storing the uploaded files on the server and retrieving them.

In the service package, create a new Java class named FileService:

package com.example.springbootreactfile.service;

import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

@Service
public class FileService {

    private final Path fileStorageLocation = Paths.get("uploads").toAbsolutePath().normalize();

    public FileService() {
        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (Exception ex) {
            throw new RuntimeException("Could not create the directory where the uploaded files will be stored.", ex);
        }
    }

    public String storeFile(MultipartFile file) {
        // Normalize file name
        String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();

        try {
            // Copy file to the target location (Replacing existing file with the same name)
            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation);

            return fileName;
        } catch (IOException ex) {
            throw new RuntimeException("Could not store file " + fileName + ". Please try again!", ex);
        }
    }

    public Resource loadFileAsResource(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            if (resource.exists()) {
                return resource;
            } else {
                throw new RuntimeException("File not found " + fileName);
            }
        } catch (Exception ex) {
            throw new RuntimeException("File not found " + fileName, ex);
        }
    }
}

Explanation:

  • Path Initialization: The fileStorageLocation is initialized to the "uploads" directory in the project root. This is where the files will be stored.
  • Directory Creation: In the constructor, we create the "uploads" directory if it doesn't exist.
  • File Storage Method: The storeFile method stores the uploaded file with a unique name by appending a UUID to avoid conflicts. It then saves the file in the fileStorageLocation directory.
  • Load File Method: The loadFileAsResource method loads a file as a resource by its name. If the file exists, it returns the resource; otherwise, it throws an exception.

2.2 Create the File Controller

This controller will expose the REST API endpoints to handle file uploads and downloads.

In the controller package, create a new Java class named FileController:

package com.example.springbootreactfile.controller;

import com.example.springbootreactfile.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/files")
public class FileController {

    private final FileService fileService;

    @Autowired
    public FileController(FileService fileService) {
        this.fileService = fileService;
    }

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        String fileName = fileService.storeFile(file);
        return ResponseEntity.ok("File uploaded successfully: " + fileName);
    }

    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        Resource resource = fileService.loadFileAsResource(fileName);

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
}

Explanation:

  • File Upload Endpoint: The /upload endpoint handles POST requests to upload files. It receives a MultipartFile and stores it using the FileService.
  • File Download Endpoint: The /download/{fileName} endpoint handles GET requests to download files. It receives the file name as a path variable and loads the file as a resource using the FileService.
  • Response: The upload endpoint returns a success message with the stored file name. The download endpoint returns the file as an attachment in the response.

2.3 Update application.properties

Ensure the application.properties file is set up correctly to handle file uploads:

# File upload properties
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=2MB

Step 3: Creating the Frontend with React

3.1 Set Up React Project

  1. Open a terminal and navigate to your workspace directory.

  2. Create a new React project using Create React App:

    npx create-react-app react-frontend
    
  3. Navigate to the project directory:

    cd react-frontend
    

3.2 Install Axios

Install Axios to make HTTP requests:

npm install axios

3.3 Install Bootstrap

Install Bootstrap for styling:

npm install bootstrap

3.4 Create Components

Create the necessary components for the file upload and download functionalities.

3.4.1 Create FileService.js

Create a new file FileService.js in the src directory to handle API requests:

import axios from 'axios';

const API_BASE_URL = "http://localhost:8080/api/files";

class FileService {
    uploadFile(file) {
        const formData = new FormData();
        formData.append("file", file);

        return axios.post(`${API_BASE_URL}/upload`, formData, {
            headers: {
                "Content-Type": "multipart/form-data",
            },
        });
    }

    downloadFile(fileName) {
        return axios.get(`${API_BASE_URL}/download/${fileName}`, {
            responseType: 'blob',
        });
    }
}

export default new FileService();

Explanation:

  • API Base URL: The base URL for the API endpoints.

  • uploadFile Method: This method takes a file as input, creates a FormData object, and sends a POST request to the backend API to upload the file.

  • downloadFile Method: This method takes a file name as input and sends a GET request to the backend API to download the file.

3.4.2 Create FileUploadComponent.js

Create a new file FileUploadComponent.js in the src/components directory:

import React, { useState } from 'react';
import FileService from '../FileService';
import 'bootstrap/dist/css/bootstrap.min.css';

const FileUploadComponent = () => {
    const [selectedFile, setSelectedFile] = useState(null);
    const [message, setMessage] = useState('');

    const handleFileChange = (e) => {
        setSelectedFile(e.target.files[0]);
    };

    const handleFileUpload = async (e) => {
        e.preventDefault();
        try {
            const response = await FileService.uploadFile(selectedFile);
            setMessage(response.data);
        } catch (error) {
            setMessage('File upload failed!');
        }
    };

    return (
        <div className="container mt-5">
            <div className="row justify-content-center">
                <div className="col-md-6">
                    <div className="card">
                        <div className="card-header">File Upload</div>
                        <div className="card-body">
                            {message && <div className="alert alert-info">{message}</div>}
                            <form onSubmit={handleFileUpload}>
                                <div className="form-group">
                                    <label>Choose file</label>
                                    <input
                                        type="file"
                                        className="form-control"
                                        onChange={handleFileChange}
                                    />
                                </div>
                                <button type="submit" className="btn btn-primary mt-3">Upload</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default FileUploadComponent;

Explanation:

  • File Selection: The handleFileChange method sets the selected file to the component's state when a file is chosen.
  • File Upload: The handleFileUpload method sends the selected file to the backend API using FileService and handles the response or error messages.
  • UI: The component renders a form with a file input and a submit button. It displays messages based on the file upload result.

3.4.3 Create FileDownloadComponent.js

Create a new file FileDownloadComponent.js in the src/components directory:

import React, { useState } from 'react';
import FileService from '../FileService';
import 'bootstrap/dist/css/bootstrap.min.css';

const FileDownloadComponent = () => {
    const [fileName, setFileName] = useState('');
    const [message, setMessage] = useState('');

    const handleFileChange = (e) => {
        setFileName(e.target.value);
    };

    const handleFileDownload = async (e) => {
        e.preventDefault();
        try {
            const response = await FileService.downloadFile(fileName);
            const url = window.URL.createObjectURL(new Blob([response.data]));
            const link = document.createElement('a');
            link.href = url;
            link.setAttribute('download', fileName);
            document.body.appendChild(link);
            link.click();
            setMessage('File downloaded successfully!');
        } catch (error) {
            setMessage('File download failed!');
        }
    };

    return (
        <div className="container mt-5">
            <div className="row justify-content-center">
                <div className="col-md-6">
                    <div className="card">
                        <div className="card-header">File Download</div>
                        <div className="card-body">
                            {message && <div className="alert alert-info">{message}</div>}
                            <form onSubmit={handleFileDownload}>
                                <div className="form-group">
                                    <label>Enter file name</label>
                                    <input
                                        type="text"
                                        className="form-control"
                                        value={fileName}
                                        onChange={handleFileChange}
                                    />
                                </div>
                                <button type="submit" className="btn btn-primary mt-3">Download</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default FileDownloadComponent;

Explanation:

  • File Name Input: The handleFileChange method sets the file name to the component's state when a file name is entered.
  • File Download: The handleFileDownload method sends the file name to the backend API using FileService and handles the response or error messages. If the download is successful, it creates a link to download the file and triggers a click event to download it.
  • UI: The component renders a form with an input for the file name and a submit button. It displays messages based on the file download result.

3.4.4 Create App.js

Modify the App.js file to include the file upload and download components:

import React from 'react';
import FileUploadComponent from './components/FileUploadComponent';
import FileDownloadComponent from './components/FileDownloadComponent';
import 'bootstrap/dist/css/bootstrap.min.css';

const App = () => {
    return (
        <div className="container">
            <FileUploadComponent />
            <FileDownloadComponent />
        </div>
    );
};

export default App;

3.4.5 Update index.js

Ensure the index.js file is set up correctly:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

Step 4: Running the Application

4.1 Run the Spring Boot Application

  1. Open the SpringBootReactFileApplication class in the src/main/java/com/example/springbootreactfile directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

4.2 Run the React Application

  1. Open a terminal and navigate to the react-frontend directory.

  2. Start the React application:

    npm start
    
  3. Open your web browser and navigate to http://localhost:3000.

You can now use the file upload and download functionalities provided by the React frontend and Spring Boot backend.

Conclusion

In this tutorial, we created a full-stack application using Spring Boot for the backend and React (with functional components and hooks) for the frontend. We implemented both file upload and download functionalities and created simple forms for uploading and downloading files with React. This setup provides a solid foundation for developing more complex full-stack applications with file upload and download capabilities.


Comments