Kamal
Kamal

Reputation: 395

node javascript file upload doesn't work on remote server

On my local dev machine accessing localhost the following code works beautifully even with network settings changed to "Slow 3G." However, when running on my VPS, it fails to process the file on the server. Here are two different codes blocks I tried (again, both work without issue on local dev machine accessing localhost)

  profilePicUpload: async (parent, args) => {
     const file = await args.file;
     const fileName = `user-${nanoid(3)}.jpg`;
     const tmpFilePath = path.join(__dirname, `../../tmp/${fileName}`);

     file
        .createReadStream()
        .pipe(createWriteStream(tmpFilePath))
        .on('finish', () => {
          jimp
            .read(`tmp/${fileName}`)
            .then(image => {
              image.cover(300, 300).quality(60);
              image.writeAsync(`static/uploads/users/${fileName}`, jimp.AUTO);
            })
            .catch(error => {
              throw new Error(error);
            });
        });
}

It seems like this code block doesn't wait long enough for the file upload to finish since if I check the storage location on the VPS, I see this:

I also tried the following with no luck:

profilePicUpload: async (parent, args) => {
      const { createReadStream } = await args.file;
      let data = '';

      const fileStream = await createReadStream();

      fileStream.setEncoding('binary');

// UPDATE: 11-2
      let i = 0; 
      fileStream.on('data', chunk => {
        console.log(i);
        i++;
        data += chunk;
      });

      fileStream.on('error', err => {
        console.log(err);
      });
// END UPDATE


      fileStream.on('end', () => {
        const file = Buffer.from(data, 'binary');
        jimp
          .read(file)
          .then(image => {
            image.cover(300, 300).quality(60);
            image.writeAsync(`static/uploads/users/${fileName}`, jimp.AUTO);
          })
          .catch(error => {
            throw new Error(error);
          });
      });
}

With this code, I don't even get a partial file.

jimp is a JS library for image manipulation.

If anyone has any hints to get this working properly, I'd appreciate it very much. Please let me know if I'm missing some info.

Upvotes: 0

Views: 277

Answers (1)

Kamal
Kamal

Reputation: 395

I was able to figure out a solution by referring to this article: https://nodesource.com/blog/understanding-streams-in-nodejs/

Here is my final, working code:

const { createWriteStream, unlink } = require('fs');
const path = require('path');
const { once } = require('events');
const { promisify } = require('util');
const stream = require('stream');
const jimp = require('jimp');

profilePicUpload: async (parent, args) => {
      // have to wait while file is uploaded
      const { createReadStream } = await args.file;

      const fileStream = createReadStream();
      const fileName = `user-${args.uid}-${nanoid(3)}.jpg`;
      const tmpFilePath = path.join(__dirname, `../../tmp/${fileName}`);
      const tmpFileStream = createWriteStream(tmpFilePath, {
        encoding: 'binary'
      });

      const finished = promisify(stream.finished);

      fileStream.setEncoding('binary');

      // apparently async iterators is the way to go
      for await (const chunk of fileStream) {
        if (!tmpFileStream.write(chunk)) {
          await once(tmpFileStream, 'drain');
        }
      }

      tmpFileStream.end(() => {
        jimp
          .read(`tmp/${fileName}`)
          .then(image => {
            image.cover(300, 300).quality(60);
            image.writeAsync(`static/uploads/users/${fileName}`, jimp.AUTO);
          })
          .then(() => {
            unlink(tmpFilePath, error => {
              console.log(error);
            });
          })
          .catch(error => {
            console.log(error);
          });
      });

      await finished(tmpFileStream);
}

Upvotes: 1

Related Questions