Transferring PDF files between Vue front-end and Spring Backend via Axios for seamless uploading and downloading

With spring-boot 2.0.4 on the back-end and vue 2.5.16 / axios 0.18.0 on the front-end, my goal is to upload PDF files to the back-end database and retrieve them from the front-end.

I initially took inspiration from this example for the spring part:

And this gist for the Axios part: https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743

Below is a snippet of my code:

  • To upload a file in Vue using axios (the 'this.file' variable holds the file selected via an "input type='file'" form input, AxiosService() sets the base URL and includes an Authorization header with a token):

    createRecord() {
      let formData = new FormData();
      formData.append("file", this.file);
      AxiosService()
        .post("/commands/material/", formData, {
           headers: {
             "Content-Type": "multipart/form-data"
           }
      })
      .then(response => {
        console.log("File uploaded");
      })
      .catch(error => {
        console.log(error);
      });
    
  • The section in the spring part handling the upload looks as follows. In my Entity, the content field is defined as a byte[] annotated with @Lob.

    @BasePathAwareController
    @RequestMapping("/commands/material")
    public class MaterialCommandHandler {
        @Autowired
        MaterialRepository MaterialRepository;
    
        @RequestMapping(method=POST, path = "/")
        public ResponseEntity create(@RequestParam("file") MultipartFile file){
            MaterialEntity material = new MaterialEntity();
            material.setName(file.getOriginalFilename());
            material.setMimetype(file.getContentType());
    
            try {
                material.setContent(file.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            try {
                MaterialRepository.save(material);
            } catch (Exception e) {
                if (e instanceof DataIntegrityViolationException) {
                    throw new InvalidCommandException("Data is invalid for creation.");
                }
                throw(e);
            }
            return ResponseEntity.status(HttpStatus.CREATED).body(material.getId());
    }
    

Using this code, the entry is successfully created in the DB, with the content field in MySQL being of type longblob.

  • The method designed to return the content of the file:

    @RequestMapping(method = GET, path = "/download/{fileId}")
    public ResponseEntity<byte[]> getFile(@PathVariable Long fileId) {
        Optional<MaterialEntity> fileOptional = materialRepository.findById(fileId);
    
        if(fileOptional.isPresent()){
            FrancaisMaterialEntity file = fileOptional.get();
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachement; filename=\"" + file.getName() + "\"");
            return ResponseEntity.ok()
                .headers(headers)
                .body(file.getContent());
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }
    
  • Lastly, the GET method sent from the front-end using axios:

    downloadFile() {
        AxiosService()
          .get(`/commands/material/download/${this.material.id}`, {
            responseType: "blob",
          })
          .then(response => {
            console.log(response);
            const url = window.URL.createObjectURL(new Blob([response.data]));
            const link = document.createElement("a");
            link.href = url;
            link.setAttribute("download", "CalculRanking.pdf");
            document.body.appendChild(link);
            link.click();
            link.parentNode.removeChild(link);
          })
          .catch(error => {
            console.log(error);
            this.errorMessage = error.response.data.message;
          });
      }
    

When attempting to download the file, the browser pop-up appears correctly but unfortunately, the downloaded .pdf file seems to be broken with Chrome stating: "Error Failed to load PDF document," and I cannot open it in Preview either.

I suspect the issue may be due to incorrect content interpretation at some point during the process. Despite trying various solutions like adding the "Accept" header with value "application/pdf" and setting "responseType: arrayBuffer" in the get request, none have worked so far. Therefore, I am reaching out for assistance here. Thank you in advance for your help.

Answer №1

Is it possible for you to update the getFile method in this way:

@GetMapping("/download/{fileId}")
@CrossOrigin
@ResponseBody
public ResponseEntity<InputStreamResource> getFile(@PathVariable(required = true, value = "fileId") Long fileId,
        HttpServletRequest request) throws IOException {

    Optional<MaterialEntity> fileOptional = materialRepository.findById(fileId);
    if (ensemblesRegles.isPresent()) {
        String fileName = "example.xlsx";
        MediaType mediaType = MediaType.parseMediaType("application/vnd.ms-excel");
        File file = new File(fileName); //the fileUtils is org.apache.commons.io.FileUtils;
        FileUtils.writeByteArrayToFile(file, fileOptional.get()); // Hoping that your get method returns a byte[] array 
        InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

        return ResponseEntity.ok()
                // Content-Disposition
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
                // Content-Type
                .contentType(mediaType)
                // Contet-Length
                .contentLength(file.length()) //
                .body(resource);
    }
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

Answer №2

If you're looking for a solution that seamlessly integrates content objects with Spring Data Entities, I highly recommend checking out Spring Content. This tool is specifically designed to streamline the process of associating one or more content objects with Spring Data Entities.

Integrating this into your existing Spring Boot project is simple. Here's how you can do it:

pom.xml

   <!-- Java API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-jpa-boot-starter</artifactId>
      <version>0.4.0</version>
   </dependency>

   <!-- REST API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest-boot-starter</artifactId>
      <version>0.4.0</version>
   </dependency>

MaterialEntity.java

@Entity
public class MaterialEntity {
   @Id
   @GeneratedValue
   private long id;

   ...other existing fields...

   @ContentId
   private String contentId;

   @ContentLength
   private long contentLength = 0L;

   @MimeType
   private String mimeType = "text/plain";

   ...
}

MaterialEntityContentStore.java

@StoreRestResource(path="materialEntityContents")
public interface MaterialEntityContentStore extends ContentStore<MaterialEntity, String> {
}

By following these steps, you'll have access to REST Endpoints that allow you to easily store and retrieve content associated with each MaterialEntity. The process works similarly to Spring Data - with Spring Content detecting your desired content storage preferences based on the provided dependencies.

For instance, you can upload an image with a command like:

curl -X POST /materialEntityContents/{materialEntityId} -F "file=@/path/to/image.jpg"

This will save the image in the database and link it to the specified material entity ID.

If you wish to explore different storage options such as filesystem or S3, simply swap out the appropriate dependency within the spring-content-jpa-boot-starter module. Examples showcasing various storage methods are available here.

While there aren't specific Vue.js examples, you can refer to an AngularJS example here for frontend guidance, as the technologies share similarities.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is your strategy for managing errors when utilizing xui's xhr functionality?

Recently, I've been utilizing the code below to extract data from an external source. <script type="text/javascript"> xui.ready(function() { var url = 'http://www.domain.com/getData.aspx'; x$().xhr(url, ...

How to Utilize Puppeteer for Redirecting Pages While Preserving Cache?

Currently, I am facing a challenge with my code in JavaScript that is responsible for opening a page and logging in. Once Puppeteer finishes the login process, I need it to redirect to another page on the same site without clearing the cache. I attempted ...

Ways to make nodejs return a different value instead of an object promise

Within my user.js file, I have an async function designed to retrieve the avatar's location from the database. The code for this operation is as follows: async findavatar(username) { const sql = `SELECT image FROM users WHERE user = "${userna ...

Angular component - associated calls

I've created a unique custom directive specifically for the widget attribute shown below: <div widget></div> Essentially, this directive generates template code and inserts the html inside the widget-tag. Here's a snippet of the dir ...

Is there a way to boost the scrolling speed of the div element?

I'm currently working on a parallax web design and I need some help. Does anyone know how I can make the red div move faster? I believe it has to do with the formula but I'm not entirely sure. Can someone assist me with this? Check out my code: ...

Hold up in paths with identical modules

Utilizing vue router for navigating with transitions and vuex to fetch and store data from a JSON API has been successful. The issue arises on routes that utilize the same component. After a route change, there is a momentary display of the old state of t ...

Cross-component communication in Angular

I'm currently developing a web-based application using angular version 6. Within my application, there is a component that contains another component as its child. In the parent component, there is a specific function that I would like to invoke when ...

Utilize the requestAnimationFrame function to dynamically determine the size and color

My initial goal was to develop a program that generates a circle, gradually shrinking until it vanishes. This circle was supposed to continuously repeat this animation while transitioning between colors (starting from one color and ending in another). Al ...

Breaking down an array into alphabetical sections using React and Typescript

I have a large array of strings containing over 100 words. I have already sorted the array in alphabetical order, but now I need to group or split the array into a hash array called hashMap<String(Alphabet), List<String>>. This will allow me to ...

Create a few documents, and duplicate the remainder

I need help with a straightforward project. In my directory, I have some bash files and JavaScript files in the src/ folder. My aim is to transpile the JS files and copy over the rest of the files. Here's how my folders are structured: root |- dist ...

Using setInterval in an ES6 class

After creating the following object, everything seemed to be working fine until I introduced setinterval. It became apparent that this was now referencing the object timeout class test{ constructor(){ this.counter = 0 } startCounter() ...

Making a dropdown menu spin using Jquery and Javascript

I am in need of a unique solution for a dropdown menu that appears rotated 90 degrees anticlockwise. The goal is to have the dropdown select "button" text displayed vertically, with the options sliding out in a similarly rotated, sideways manner featuring ...

Creating personalized tags or components by utilizing insertAdjacentHTML in Vue

With the use of insertAdjacentHTML, I am able to create HTML elements such as div and span, but it seems that I cannot generate custom tags like <myComponent> </myComponent>. I understand that insertAdjacentHTML can create HTML elements, but a ...

Serialization of Passport Session failed due to a Bad Gateway error

I am currently utilizing the following dependencies: express session passport passport-local passport-local-mongoose Upon registering a user and posting the data, the information is successfully saved to the database, but a bad request error is generated ...

JavaScript effect to make a div with a YouTube video fade in and out

I have limited knowledge of javascript, but I am curious if this idea can be achieved: I want to make the videos on my webpage invisible initially. When a user clicks on one of my links, I want a YouTube embed to fade in and start playing. Then, when the ...

Include the JS file after finishing the control processing

I've been grappling with an issue for several days now. The view I have is populated by my controller through an API call, which works perfectly fine in rendering the HTML. $http.get(url). success(function(data, status, headers, config) { ...

Sorting an array of strings based on user input using Vue.js

I'm facing an issue where I have an array of various strings: [ "Aluminum", "Basic Materials", "Broad Credit", "Broad Market", "Cocoa", "Coffee", "Consumer Cyclicals", "Consumer Non-cyclicals", "Copper", "Corn", "Cotton", "Crude Oil", "Energy", "Exte ...

Guide to Dynamically Including an Element in an Array using Typescript

Encountering a type error within the <RenderFormFields formFields={formFieldsData} /> component:- Types of property 'type' are not compatible. Type 'string' cannot be assigned to type '"select"'.ts(2322) Rende ...

Is it possible to display a React Component code within a <code> tag?

I am in the process of creating a tester page that allows users to interact with a library component and document how it is being used. Here is the library component: render = () => { let component = ( <Slider onSlid ...

The markers are refusing to appear on the map, despite the fact that the data is successfully being retrieved from

<?php error_reporting(E_ALL ^ E_DEPRECATED); $conn = mysql_connect("localhost", "root", "root") or die(mysql_error()); mysql_select_db("db_jawandsons") or die(mysql_error()); ?> <html> <head> <meta http ...