Search code examples
node.jsfirebasegoogle-cloud-firestorebusboy

How do I push a new array index into a database property, keeping the data already stored untouched?


I have some code that uploads an image as well as updates a image URL array property named 'images' where each image url is stored within indexes of the array.

I have the function below where I tried to use db.doc(`/posts/${req.params.postId}`).update({ images: images.push(image) });

but I was met with an error. Does anyone have a simple way to do this? I really appreciate any help!

exports.uploadImage = (req, res) => {

  // res.send("this worked"); // everything works up to this point

  const Busboy = require("busboy");

  const path = require("path");

  const os = require("os");

  const fs = require("fs");

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

  let imageToBeUploaded = {};
  let imageFileName;
  // res.send("this worked");
  busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
    console.log(fieldname, file, filename, encoding, mimetype);
    if (mimetype !== "image/jpeg" && mimetype !== "image/png") {
      return res.status(400).json({ error: "Wrong file type submitted" });
    }
    // my.image.png => ['my', 'image', 'png']
    const imageExtension = filename.split(".")[filename.split(".").length - 1];
    // 32756238461724837.png
    imageFileName = `${Math.round(
      Math.random() * 1000000000000
    ).toString()}.${imageExtension}`;
    const filepath = path.join(os.tmpdir(), imageFileName);
    imageToBeUploaded = { filepath, mimetype };
    file.pipe(fs.createWriteStream(filepath));

  });
  busboy.on("finish", () => {
    admin
      .storage()
      .bucket()
      .upload(imageToBeUploaded.filepath, {
        resumable: false,
        metadata: {
          metadata: {
            contentType: imageToBeUploaded.mimetype
          }
        }
      })
      .then(() => {
        const image = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;
        return db.doc(`/posts/${req.params.postId}`).update({ images: **images.push(image)** });
      })
      .then(() => {
        return res.json({ message: "image uploaded successfully" });
      })
      .catch(err => {
        console.error(err);
        return res.status(500).json({ error: "something went wrong" });
      });
  });
  busboy.end(req.rawBody);
};


Solution

  • If you want to keep an array of unique values in the images field, you can use the array-union operation. From the documentation on updating an array:

    let admin = require('firebase-admin');
    // ...
    let washingtonRef = db.collection('cities').doc('DC');
    
    // Atomically add a new region to the "regions" array field.
    let arrUnion = washingtonRef.update({
      regions: admin.firestore.FieldValue.arrayUnion('greater_virginia')
    });
    // Atomically remove a region from the "regions" array field.
    let arrRm = washingtonRef.update({
      regions: admin.firestore.FieldValue.arrayRemove('east_coast')
    });
    

    If you were to call washingtonRef.update({ regions: admin.firestore.FieldValue.arrayUnion('greater_virginia') }) multiple times on the same document, the regions array in that doc will still only contain greater_virginia once.

    This is the only way to add a value to an array without knowing the existing items in the array. The only one way to update an array is by first reading that array, then adding your value to it in your code, and finally writing the result back to the Firestore.