1

I am trying to upload a pdf from the frontend to my node server. The PDF successfully uploads on the node server but when I go to open it, I am unable to. Instead, I see a message that says "File cant be opened. Something went wrong." Why is this happening?

Also please dont suggest third party pdf uploaders like multer, etc. I am aware of these third party libraries but I just want pure node. Thank you so much.

Frontend code:

const uploadFile = document.getElementById("uploadFile");

uploadFile.addEventListener("change", (event) => {
  readFile(event.target.files[0]);
});

function readFile(file) {
  const uploadDesignPDF = `http://localhost:7000/api/upload/design`;
  let fileReader = new FileReader();
  fileReader.readAsDataURL(file);
  fileReader.addEventListener("load", async (event) => {
    let pdfStrChunk = event.target.result.replace(
      /^data:application\/[a-z]+;base64,/,
      ""
    );
    let fileSize = file.size;
    const chunk = 85000;
    let numOfChunkSet = Math.ceil(fileSize / chunk);
    let remainingChunk = fileSize;
    let currentChunk = 0;
    let chunkSet = [];
    let range = {};
    let data = {};

    for (let i = 0; i < numOfChunkSet; i++) {
      remainingChunk -= chunk;

      if (remainingChunk < 0) {
        remainingChunk += chunk;
        chunkSet.push(remainingChunk);
        range.start = currentChunk;
        range.end = currentChunk + chunk;
        currentChunk += remainingChunk;
      } else {
        chunkSet.push(chunk);
        range.start = currentChunk;
        range.end = (i + 1) * chunkSet[i];
        currentChunk += chunk;
      }

      const chunkRead = pdfStrChunk.slice(range.start, range.end);
      data.dataPDF = chunkRead;

      let response = await fetch(uploadDesignPDF, {
        method: "POST",
        body: JSON.stringify(data),
        headers: {
          "Content-Type": "application/json",
        },
        responseType: "arrayBuffer",
        responseEncoding: "binary",
      });
      let results = await response.json();
      console.log(results);
    }
  });
}

Backend route:

const { uploadDesigns } = require("./upload.designs.controller.js");
const router = require("express").Router();

router.post("/upload/design", uploadDesigns);

Backend:

  uploadDesigns: async (req, res) => {
    try {
      fs.writeFileSync(`./designs/testingPDF6.pdf`, req.body.dataPDF, "base64");
      res.status(200).json({
        message: "done with chunk",
      });
    } catch (error) {
      res.status(500).json({
        message: "Something went wrong. Please refresh page.",
      });
    }
  }
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
zachjohn987
  • 103
  • 7
  • Slightly confused by your frontend code: that's a lot of code to just send a file to an endpoint. Any reason you're not just straight up `POST`ing that `file` object from the input change event, with one extra property `dataUri: ...` that's just the base64 data-uri that you get from `readAsDataURL`? (no need for chunking, just send the data as a normal POST payload). And then on the server side you can do an fs.writeFile with the correct filename, and the content [coming from the data-uri](https://stackoverflow.com/a/11335500/740553). – Mike 'Pomax' Kamermans Jul 05 '22 at 22:36
  • 1
    Thank you for your response, Mike. The extra bit of code is to create a chunk set that will be sent to the server in chunks because if I send it all at once then error 413 "payload is too large" error occurs. So thats why I send it as chunks – zachjohn987 Jul 05 '22 at 22:52
  • Fair enough - that's some really big PDF files then though =D – Mike 'Pomax' Kamermans Jul 05 '22 at 23:28
  • Yep, its an engineering design in a pdf :) – zachjohn987 Jul 05 '22 at 23:45

1 Answers1

1

You are working with base64-URL in vain. It is much more effective to use ArrayBuffer. The main advantage of ArrayBuffer is the 1-byte unit, while base64 breaks the byte representation three out of four times.

Instead of sending the file in chunks, I would suggest tracking progress through XMLHttpRequest.upload.onprogress(). I would only use chunks if the upload is through a WebSocket.

If the PDF file is the only information sent to the server, I'd prefer to send the file directly without any field names or other FormData information provided. In that case, it would be appropriate to change the POST method to PUT.

If you prefer to send the file directly, it would be ideal to use fs.createWriteStream() instead of fs.writeFileSync(). Then this approach will work

  const ws = fs.createWriteStream(tmpFilePath);
  request.pipe(ws);

To control the integrity of the data, you can add md5 or sha hash to the request headers and, on the server, duplicate the data stream into the object created by crypto.createHash(). In case of a hash mismatch, the file can be uploaded again.

DiD
  • 173
  • 8