createUpload fails with 'Missing Filename'

We have a project where we need to create be able to create records with associated media files. We are using Nuxt 3 and server routes to provide an endpoint for the front end to do this. The general data is working fine but I am struggling with the media files (images).

We have a specific end point to create the uploads and return a list of upload id’s to use when creating the main record (code below).

The project is hosted with Netlify and so the code will ultimately run as a serverless function. Because we can’t create local files in this environment we are attempting to use the cma-client-browser and create the uploads using blobs.

I’m not an expert on Node backend code by any means and potentially it could be that I’m just missing something obvious as I’m aware the cma-client-browser package is meant to be used in a front end environment not a node environment.

Anyway - the simplified code that runs on the end point is below. The annoying thing for us is that the error is always about the filename but we are providing this option to the createUpload function so it obviously isn’t the issue.

From our logging/testing the file blobs are being created correctly and filename has the correct value.

Any help on the right approach for this would be much appreciated.

import { buildClient } from "@datocms/cma-client-browser";

export default defineEventHandler(async (event) => {
  // Set up
  const config = useRuntimeConfig(event)
  const client = buildClient({ apiToken: config.datoPaeKey });

  // Get the body of the request
  const form = await readMultipartFormData(event);
  if (!form) {
    return { error: 'No form data found' }
  }
  console.log(form)
// Variable for errors
  let resError

  // Function to handle creating the upload records
  async function createUpload(blob: Blob, filename: string) {
    console.log(blob, filename)
    try {
      const upload = await client.uploads.createFromFileOrBlob({
        // File object to upload
        fileOrBlob: blob,
        tags: ['user-uploaded'],
        filename: filename,
        // specify some additional metadata to the upload resource
      });
      return upload
    } catch (error) {
      resError = error
      console.error(error);
    }
  }

// Loop over the data and call the create upload function.
  const fileIds: string[] = [];
  if (typeof form !== 'undefined' && !!form?.length) {
    for (let file of form) {
      if (file.name === 'file') {
        const filename = file.filename;
        const blob = new Blob([file.data], { type: file.type });
        const upload = await createUpload(blob, filename || 'test' + file.type);
        if (!!upload?.id) {
          fileIds.push(upload.id);
        }
      }
    }
  }

  return { resError, fileIds }
})

Hi @tim1,

At first glance there’s nothing obviously wrong with your code. Does it work if you don’t specify the filename at all?

Also, for const filename = file.filename, does it make a difference if you use file.name instead? File: name property - Web APIs | MDN

If none of that works, could you please log the outgoing request on the route (using the LogLevel.BODY option of buildClient()) and also the exact error you get back? It’s a bit hard to troubleshoot without the specifics.

Thanks @roger - appreciate the response.

The files are coming from formdata and so the ‘name’ property is reserved for the field name and the name of the file ends up in ‘filename’. As per below the file name is coming through just time to the createUpload function.

The tip for logging the body is useful. when I do that I get the below. It looks like filename is definitely being passed in and so is the blob.

Here’s the output in the terminal:

error Error: Missing filename, please provide it as an option!
    at uploadFileOrBlobAndReturnPath (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/src/utils/uploadFileOrBlobAndReturnPath.ts:38:11)
    at Upload.<anonymous> (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/src/resources/Upload.ts:49:54)
    at step (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/dist/cjs/resources/Upload.js:59:23)
    at Object.next (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/dist/cjs/resources/Upload.js:40:53)
    at /Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/dist/cjs/resources/Upload.js:34:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/dist/cjs/resources/Upload.js:30:12)
    at <anonymous> (/Users/timchesney/sites/paererewa/node_modules/@datocms/cma-client-browser/src/resources/Upload.ts:42:7)
    at <anonymous> (/Users/timchesney/sites/paererewa/node_modules/@datocms/rest-client-utils/src/makeCancelablePromise.ts:32:11)
    at new Promise (<anonymous>)
{
  name: 'file',
  filename: 'ScreenShot 2024-03-04 at 10.11.28@2x.png',
  type: 'image/png',
  data: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 0a 02 00 00 02 5e 08 06 00 00 00 7c ca 8c 05 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 00 96 ... 187335 more bytes>
}

The second half of that is the new logging of the body of the request. Which looks pretty good to me.

To test this another way I tried using the same library in the browser. I was able to create an upload when I used the File directly from the file field, but when I converted this to a blob and tried to upload it I got the same error even when providing filename.

Does the createUpload function definitely support blobs - perhaps there is a bug here? I know that option is called fileOrBlob but it doesn’t seem to like Blobs very much!

Hi @tim1,

I’m very, very sorry again for the long delay on this!

You were right: There was indeed a bug in our library: Fix impossible blob name check by arcataroger · Pull Request #19 · datocms/js-rest-api-clients · GitHub. Sorry about that! I have submitted a PR to hopefully fix it and properly allow blob uploads.

Unfortunately, even after that fix, cma-client-browser still won’t on Node.js without a polyfill for XmlHttpRequest, which is used later on in the upload process. You can of course fork that repo and apply that polyfill yourself if you’d like, or alternatively, it might be easier to just send the raw HTTP requests:

Right - I think it would be worth Dato providing an option to provide the image as Base64 encoded like Cloudinary does in the node client. This makes it much simpler to pass file data to the backend using the FileReader api to encode the file.

const readFile = (file: File) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      resolve(reader.result);
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
};

The resulting encoded file can be passed to the backend as part of a json object (rather than dealing with FormData).

On the backend this is passed directly from the body of the request to Cloudinary which makes it very easy to handle and manage. Plus Cloudinary returns a response very rapidly even fro fairly large images.