0

I created an API below:

app.post("/categories", async (req, res) => {
  console.log(`req.body: ${JSON.stringify(req.body)}`)
  console.log(`req.body.title: ${JSON.stringify(req.body.title)}`)
  console.log(`req.files: ${JSON.stringify(req.files)}`)
  res.json({})
});

Where the data passed is:

{
    "title": "Video Title"
    "description": "Video Description"
    "thumbnail": [object File]
    "video": [object File]
}

The data passed is powered by VueJS and Axios:

methods: {
    async createCategory() {
      const formData = new window.FormData();
      formData.set("title", this.category.title);
      formData.set("description", this.category.description);
      formData.set("thumbnail", this.thumbnail);
      formData.set("video", this.video);
      await $this.axios.post("clothing/v1/categories/", formData, {
        headers: { "Content-Type": "multipart/form-data" },
      });
    }
}

However the shown data in the req.body is:

req.body: {"type":"Buffer","data":[45,45,45,45,45,45,87,101,98,75,105,116,70,111,114,109,66,111,117,110,100,97,114,121,121,104,112,52,54,97,82,89,68,121,77,82,57,66,52,110,13,10,67,111,110,116,101,110,116,45,68,105,115,112,111,115,105,116,105,111,110,58,32,102,111,114,109,45,100,97,116,97,59,32,110,97,109,101,61,34,116,105,116,108,101,34,13,10,13,10,86,105,100,101,111,32,84,105,116,108,101,13,10,45,45,45,45,45,45,87,101,98,75,105,116,70,111,114,109,66,111,117,110,100,97,114,121,121,104,112,52,54,97,82,89,68,121,77,82,57,66,52,110,13,10,67,111,110,116,101,110,116,45,68,105,115,112,111,115,105,116,105,111,110,58,32,102,111,114,109,45,100,97,116,97,59,32,110,97,109,101,61,34,100,101,115,99,114,105,112,116,105,111,110,34,13,10,13,10,86,105,100,101,111,32,68,101,115,99,114,105,112,116,105,111,110,13,10,45,45,45,45,45,45,87,101,98,75,105,116,70,111,114,109,66,111,117,110,100,97,114,121,121,104,112,52,54,97,82,89,68,121,77,82,57,66,52,110,13,10,67,111,110,116,101,110,116,45,68,105,115,112,111,115,105,116,105,111,110,58,32,102,111,114,109,45,100,97,116,97,59,32,110,97,109,101,61,34,116,104,117,109,98,110,97,105,108,34,13,10,13,10,91,111,98,106,101,99,116,32,70,105,108,101,93,13,10,45,45,45,45,45,45,87,101,98,75,105,116,70,111,114,109,66,111,117,110,100,97,114,121,121,104,112,52,54,97,82,89,68,121,77,82,57,66,52,110,13,10,67,111,110,116,101,110,116,45,68,105,115,112,111,115,105,116,105,111,110,58,32,102,111,114,109,45,100,97,116,97,59,32,110,97,109,101,61,34,118,105,100,101,111,34,13,10,13,10,91,111,98,106,101,99,116,32,70,105,108,101,93,13,10,45,45,45,45,45,45,87,101,98,75,105,116,70,111,114,109,66,111,117,110,100,97,114,121,121,104,112,52,54,97,82,89,68,121,77,82,57,66,52,110,45,45,13,10]}

I am hoping that I can retrieve my passed data inside my API something like: req.body: {"title":"Example","description":"example"} as I will use these data to save in FireStore and upload the files in Cloud Storage.

NOTE: I tried using multer but got the error below:

>      return fn.apply(this, arguments);
>                ^
>
>  TypeError: Cannot read properties of undefined (reading 'apply')
>      at Immediate.<anonymous> (/Users/adminadmin/Desktop/projects/dayanara-environments/dayanara-clothing-api/functions/node_modules/express/lib/router/index.js:641:15)
>      at processImmediate (node:internal/timers:468:21)
Dean Christian Armada
  • 6,724
  • 9
  • 67
  • 116

1 Answers1

1

I did not mention that I was developing NodeJS with Google Cloud Functions and only in local and testing development.

The error below always shows whenever there is any kind of error in my code.

>      return fn.apply(this, arguments);
>                ^
>
>  TypeError: Cannot read properties of undefined (reading 'apply')
>      at Immediate.<anonymous> (/Users/adminadmin/Desktop/projects/dayanara-environments/dayanara-clothing-api/functions/node_modules/express/lib/router/index.js:641:15)
>      at processImmediate (node:internal/timers:468:21)

As for the multipart, I used busboy like below:

app.post("/categories", (req, res) => {
  let writeResult;
  const storageRef = admin.storage().bucket(`gs://${storageBucket}`);
  const busboy = Busboy({headers: req.headers});
  const tmpdir = os.tmpdir();

  // This object will accumulate all the fields, keyed by their name
  const fields = {};

  // This object will accumulate all the uploaded files, keyed by their name.
  const uploads = {};

  // This code will process each non-file field in the form.
  busboy.on('field', (fieldname, val) => {
    /**
     *  TODO(developer): Process submitted field values here
     */
    console.log(`Processed field ${fieldname}: ${val}.`);
    fields[fieldname] = val;
  });

  const fileWrites = [];

  // This code will process each file uploaded.
  busboy.on('file', (fieldname, file, {filename}) => {
    // Note: os.tmpdir() points to an in-memory file system on GCF
    // Thus, any files in it must fit in the instance's memory.
    console.log(`Processed file ${filename}`);
    const filepath = path.join(tmpdir, filename);
    uploads[fieldname] = filepath;

    const writeStream = fs.createWriteStream(filepath);
    file.pipe(writeStream);

    // File was processed by Busboy; wait for it to be written.
    // Note: GCF may not persist saved files across invocations.
    // Persistent files must be kept in other locations
    // (such as Cloud Storage buckets).
    const promise = new Promise((resolve, reject) => {
      file.on('end', () => {
        writeStream.end();
      });
      writeStream.on('finish', resolve);
      writeStream.on('error', reject);
    });
    fileWrites.push(promise);
  });

  // Triggered once all uploaded files are processed by Busboy.
  // We still need to wait for the disk writes (saves) to complete.
  busboy.on('finish', async () => {
    console.log('finished busboy')
    await Promise.all(fileWrites);

    /**
     * TODO(developer): Process saved files here
     */
    for (const file in uploads) {
      const filePath = uploads[file]
      const name = fields.name.replaceAll(' ', '-')
      const _filePath = filePath.split('/')
      const fileName = _filePath[_filePath.length - 1]
      const destFileName = `${name}/${fileName}`
      // eslint-disable-next-line no-await-in-loop
      const uploaded = await storageRef.upload(filePath, {
        destination: destFileName
      })
      const _file = uploaded[0];
      const bucketFile = "https://firebasestorage.googleapis.com/v0/b/" + storageBucket + "/o/" + encodeURIComponent(_file.name) + "?alt=media"
      fields[file] = bucketFile
    }
    writeResult = await admin
      .firestore()
      .collection(collection)
      .add({ 
        name: fields.name,
        description: fields.description,
        timestamp: admin.firestore.Timestamp.now(),
        thumbnail: fields.thumbnail,
        video: fields.video
      });
    const written = await writeResult.get();
    res.json(written.data());
  }
});

Then I needed to change how I pass formData from my VueJS and Axios where I replaced using model to refs on my file data. I only needs to use model in Django so I thought it would be the same on ExpressJS:

methods: {
    async createCategory() {
      const formData = new window.FormData();
      const thumbnail = this.$refs.thumbnail;
      const video = this.$refs.video;
      formData.set("name", this.category.name);
      formData.set("description", this.category.description);
      formData.set("thumbnail", thumbnail.files[0]);
      formData.set("video", video.files[0]);
      await $this.axios.post("clothing/v1/categories/", formData, {
        headers: { "Content-Type": "multipart/form-data" },
      });
    }
}

After the changes above, I can finally send multi-part/form-data properly. Resources below helped me a lot:

Dean Christian Armada
  • 6,724
  • 9
  • 67
  • 116