Spring Boot Vue.js File Upload and Download Example

In this tutorial, we will create a full-stack application using Spring Boot 3 for the backend and Vue.js 3.4 for the front end. The application will include functionalities for uploading and downloading files.

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: file-upload-download
    • Name: file-upload-download
    • Description: File Upload and Download Application with Spring Boot and Vue.js
    • Package Name: com.example.fileuploaddownload
    • 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 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

# File storage location
file.upload-dir=uploads

1.3 Create a File Storage Service

In the com.example.fileuploaddownload.service package, create a new Java class named FileStorageService:

package com.example.fileuploaddownload.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
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.nio.file.StandardCopyOption;

@Service
public class FileStorageService {

    private final Path fileStorageLocation;

    public FileStorageService(@Value("${file.upload-dir}") String uploadDir) {
        this.fileStorageLocation = Paths.get(uploadDir)
                .toAbsolutePath().normalize();

        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) {
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

        try {
            if (fileName.contains("..")) {
                throw new RuntimeException("Invalid path sequence " + fileName);
            }

            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

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

    public Path loadFileAsResource(String fileName) {
        return this.fileStorageLocation.resolve(fileName).normalize();
    }
}

1.4 Create the FileController Class

In the com.example.fileuploaddownload.controller package, create a new Java class named FileController:

package com.example.fileuploaddownload.controller;

import com.example.fileuploaddownload.service.FileStorageService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Path;

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

    private final FileStorageService fileStorageService;

    public FileController(FileStorageService fileStorageService) {
        this.fileStorageService = fileStorageService;
    }

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        return fileStorageService.storeFile(file);
    }

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

        try {
            resource = new UrlResource(filePath.toUri());
            if (!resource.exists()) {
                throw new RuntimeException("File not found " + fileName);
            }
        } catch (Exception e) {
            throw new RuntimeException("File not found " + fileName, e);
        }

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

Step 2: Creating the Frontend with Vue.js

2.1 Set Up Vue Project

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

  2. Create a new Vue project using Vue CLI:

    npm install -g @vue/cli
    vue create vue-frontend
    
  3. Navigate to the project directory:

    cd vue-frontend
    

2.2 Install Axios and Bootstrap

Install Axios to make HTTP requests and Bootstrap for styling:

npm install axios bootstrap

2.3 Create Components

Create the necessary components for file upload and download.

2.3.1 Create FileService.js

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

import axios from 'axios';

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

class FileService {
    uploadFile(file) {
        let 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();

2.3.2 Create UploadComponent.vue

Create a new file UploadComponent.vue in the src/components directory:

<template>
  <div class="container mt-5">
    <div class="row justify-content-center">
      <div class="col-md-6">
        <div class="card">
          <div class="card-header">Upload File</div>
          <div class="card-body">
            <form @submit.prevent="uploadFile">
              <div class="form-group">
                <input type="file" @change="selectFile" class="form-control" />
              </div>
              <button type="submit" class="btn btn-primary mt-3">Upload</button>
            </form>
            <div v-if="fileName" class="mt-3">
              <span>Uploaded file: {{ fileName }}</span>
              <button @click="downloadFile" class="btn btn-secondary ml-3">Download</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import FileService from '../FileService';

export default {
  data() {
    return {
      selectedFile: null,
      fileName: ''
    };
  },
  methods: {
    selectFile(event) {
      this.selectedFile = event.target.files[0];
    },
    uploadFile() {
      FileService.uploadFile(this.selectedFile).then(response => {
        this.fileName = response.data;
      });
    },
    downloadFile() {
      FileService.downloadFile(this.fileName).then(response => {
        const url = window.URL.createObjectURL(new Blob([response.data]));
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', this.fileName);
        document.body.appendChild(link);
        link.click();
      });
    }
  }
};
</script>

2.3.3 Create App.vue

Modify the App.vue file to include the UploadComponent:

<template>
  <div id="app">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand" href="#">File Upload and Download</a>
    </nav>
    <upload-component></upload-component>
  </div>
</template>

<script>
import UploadComponent from './components/UploadComponent.vue';

export default {
  name: 'App',
  components: {
    UploadComponent
  }
};
</script>

<style>
nav {
  margin-bottom: 20px;
}
</style>

2.3.4 Update main.js

Ensure the main.js file is set up correctly:

import { createApp } from 'vue';
import App

 from './App.vue';
import 'bootstrap/dist/css/bootstrap.min.css';

createApp(App).mount('#app');

Step 3: Running the Application

3.1 Run the Spring Boot Application

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

3.2 Run the Vue.js Application

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

  2. Start the Vue application:

    npm run serve
    
  3. Open your web browser and navigate to http://localhost:8080.

You should now be able to upload and download files using the Vue.js frontend and Spring Boot backend.

Conclusion

In this tutorial, we created a full-stack application using Spring Boot for the backend and Vue.js for the frontend. We implemented file upload and download functionalities and handled the necessary configurations to connect the two parts of the application. This setup provides a solid foundation for developing more complex full-stack applications.

Comments