1

I created function for uploading a single image on Firebase using NodeJS and Busboy, which returns image url. Allowed image extensions are only .jpg and .png. It will generate random filename and create filepath with storageBucket.

However, I am struggling to refactor this function, so I could upload multiple images. I tried several attempts, but no luck. It should return array of image urls, if all images were uploaded successfully.

Here is my function with single image upload:

const { admin, db } = require("./admin");
const config = require("./config");

exports.uploadImage = (req, res, url, folder) => {
    const BusBoy = require("busboy");
    const path = require("path");
    const os = require("os");
    const fs = require("fs");

    const busboy = new BusBoy({ headers: req.headers });

    let imageFileName;
    let imageToBeUploaded = {};

    busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
        if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
            return res
                .status(400)
                .json({ error: "Wrong file type submitted!" });
        }
        // Getting extension of any image
        const imageExtension = filename.split(".")[
            filename.split(".").length - 1
        ];
        // Setting filename
        imageFileName = `${Math.round(
            Math.random() * 1000000000
        )}.${imageExtension}`;
        // Creating path
        const filepath = path.join(os.tmpdir(), imageFileName);
        imageToBeUploaded = { filepath, mimetype };
        file.pipe(fs.createWriteStream(filepath));
    });
    busboy.on("finish", () => {
        admin
            .storage()
            .bucket()
            .upload(imageToBeUploaded.filepath, {
                destination: `${folder}/${imageFileName}`,
                resumable: false,
                metadata: {
                    metadata: {
                        contentType: imageToBeUploaded.mimetype
                    }
                }
            })
            .then(() => {
                const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o${folder}%2F${imageFileName}?alt=media`;
                if (url === `/users/${req.user.alias}`) {
                    return db.doc(`${url}`).update({ imageUrl });
                } else {
                    return res.json({ imageUrl });
                }
            })
            .then(() => {
                return res.json({
                    message: "Image uploaded successfully!"
                });
            })
            .catch(err => {
                console.log(err);
                return res.status(500).json({ error: err.code });
            });
    });
    busboy.end(req.rawBody);
};

Any suggestions how to move on?

Dromediansk
  • 21
  • 1
  • 4

2 Answers2

3

Samuel Vera's answer is almost correct. There are some typos and a logic error when push to imageUrls array.

Here, the complete code fixed:

const BusBoy = require('busboy');
const path = require('path');
const os = require('os');
const fs = require('fs');

let fields = {};

const busboy = new BusBoy({ headers: request.headers });

let imageFileName = {};
let imagesToUpload = [];
let imageToAdd = {};
let imageUrls = [];

busboy.on('field', (fieldname, fieldvalue) => {
    fields[fieldname] = fieldvalue;
});

busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
    if (mimetype !== 'image/jpeg' && mimetype !== 'image/png') {
        return res
            .status(400)
            .json({ error: 'Wrong file type submitted!' });
    }

    // Getting extension of any image
    const imageExtension = filename.split('.')[
        filename.split('.').length - 1
    ];

    // Setting filename
    imageFileName = `${Math.round(Math.random() * 1000000000)}.${imageExtension}`;

    // Creating path
    const filepath = path.join(os.tmpdir(), imageFileName);
    imageToAdd = {
        imageFileName,
        filepath,
        mimetype,
    };

    file.pipe(fs.createWriteStream(filepath));
    //Add the image to the array
    imagesToUpload.push(imageToAdd);
});

busboy.on('finish', async () => {
    let promises = [];

    imagesToUpload.forEach((imageToBeUploaded) => {
        imageUrls.push(
            `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageToBeUploaded.imageFileName}?alt=media`
        );
        promises.push(
            admin
                .storage()
                .bucket()
                .upload(imageToBeUploaded.filepath, {
                    resumable: false,
                    metadata: {
                        metadata: {
                            contentType: imageToBeUploaded.mimetype,
                        },
                    },
                })
        );
    });

    try {
        await Promise.all(promises);
        
        return response.json({
            message: `Images URL: ${imageUrls}`,
        });
        
    } catch (err) {
        console.log(err);
        response.status(500).json(err);
    }
});

busboy.end(request.rawBody);

Anyway, thank you Samuel :)

2

You've the code almost done, all you've got to do is to create an array of promises and wait for all to resolve.

let imageFileName = {}
let imagesToUpload = []
let imageToAdd = {}
//This triggers for each file type that comes in the form data
busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
    if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
        return res
            .status(400)
            .json({ error: "Wrong file type submitted!" });
    }
    // Getting extension of any image
    const imageExtension = filename.split(".")[
        filename.split(".").length - 1
    ];
    // Setting filename
    imageFileName = `${Math.round(
        Math.random() * 1000000000
    )}.${imageExtension}`;
    // Creating path
    const filepath = path.join(os.tmpdir(), imageFileName);
    imageToAdd = { 
       imageFileName
       filepath, 
       mimetype };
    file.pipe(fs.createWriteStream(filepath));
    //Add the image to the array
    imagesToUpload.push(imageToAdd);
   });

busboy.on("finish", () => {
        let promises = []
        let imageUrls = []
        imagesToUpload.forEach(imageToBeUploaded => { 
imageUrls.push(`https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o${folder}%2F${imageFileName}?alt=media`)
                    promises.push(admin
                        .storage()
                        .bucket()
                        .upload(imageToBeUploaded.filepath, {
                             destination: `${folder}/${imageFileName}`,
                             resumable: false,
                             metadata: {
                                 metadata: {
                                     contentType: imageToBeUploaded.mimetype
                                 }
                             }
                         }))
                })
          try{      
              await Promises.all(resolve)
              res.status(200).json({msg: 'Successfully uploaded all images', imageUrls})
}catch(err){ res.status(500).json(err) }
            });
        busboy.end(req.rawBody);

With that you should be able to upload them all, it's just a matter of putting all promises inside an array and use the Promise.all method to wait for them to resolve. I made it with async/await because that's how I've been doing it but I suppose you would have no problem in doing it with the callbacks.

Also the code is messy but that's mostly because I dont know how to use this text editor, I hope you can still understand it

Samuel Vera
  • 141
  • 1
  • 3
  • 1
    Thanks! It's working! Please add 'async' in your function so I can accept this answer, otherwise, it throws an error. – Dromediansk Feb 09 '20 at 10:16
  • I have already use async to my function but it's still giving me an error - https://pastebin.com/YKgCitMu – AH Rasel Apr 07 '20 at 14:49