Struggling to Get Filename to Key -> AWS S3 with Nodejs

I can’t seem to capture the filename in order to pass it as the key in my AWS S3 set up with Nodejs and Express.

Front end:

 <script src="https://transloadit.edgly.net/releases/uppy/v1.6.0/uppy.min.js"></script>
                       <script>                           
                            const AwsS3 = Uppy.AwsS3
                              var uppy = Uppy.Core()
                                .use(Uppy.Dashboard, {
                                  trigger: ".UppyModalOpenerBtn",
                                  inline: false,
                                  closeModalOnClickOutside: true,
                                  target: '#drag-drop-area',
                                  replaceTargetContent: true,
                                  showProgressDetails: true,
                                  proudlyDisplayPoweredByUppy: true,
                                  note: "Images, Word, Excel, PDF, and similar files only, max 20 files of 30 MB each",
                                  height: 470,
                                  restrictions: {
                                	maxFileSize: 31457280, //i.e. 30MB
                            		maxNumberOfFiles: 20,
                                	minNumberOfFiles: null,
                                	allowedFileTypes: null 
                            	  },
                                  metaFields: [
                            		{ id: 'name', name: 'Name', placeholder: 'You can rename the file here'},
                            		{id: 'caption', name: "Caption", placeholder: "Briefly describe what the file contains"}
                            	  ],
                                  browserBackButtonClose: true
                                })
                                .use(AwsS3, {
                                    getUploadParameters(file){
                                        return fetch('/path/to/server/', {
                                            method: 'post',
                                            headers: {
                                                'content-type': 'application/json',
                                            },
                                            body: JSON.stringify({
                                                filename: file.name,
                                                contentType: file.type
                                            })                                         
                                        }).then((response) => {                                           
                                            return response.json();
                                        }).then((data) => {
                                            console.log('>>>', data);
                                            return {
                                                method: data.method,
                                                url: data.url,
                                                fields: data.fields,
                                                headers: data.headers
                                            };
                                        });
                                    },
                                })      
                              uppy.on('complete', (result) => {
                                    console.log('Upload complete! We’ve uploaded these files:', result.successful)//<-- returns correct name under 'name:'!
                              })
                              uppy.on('upload-success', (file, data) => {
                                  console.log("file.meta['key'] is: ");
                                  console.log(file.meta['key']); //<-- this returns 'undefined'
                                  file.meta['key']
                              })
                            </script>
                        </div>

Server:

router.post("/uppytest", (req, res) => {
    console.log(req.fileName); //<-- 'undefined'
    console.log(req.body.filename); //<-- 'undefined'
    console.log(req.query.filename); //<-- 'undefined'
    console.log(req.body);//<-- '{ }'
    const params = {
        Bucket: myBucket,
        Key: `${Date.now().toString()}-${req.body.filename}`, //<-- ends up in S3 bucket as '1573509650845-undefined'      
        ContentType: req.body.contentType,
    };
    s3.getSignedUrl('putObject', params, (err, url) => {
        res.status(200).json({
            method: 'put',
            url,
            fields: {},
        });
    });
});

I’m sure I’m making some obvious mistake, but I’ve been staring at this for hours and trying everything I can think of, and I just can’t seem to snag the original file name server side.

*Edit: If I edit the file name in the Dashboard it also has no effect.

Could really use some help here, as this is driving me nuts.

Note: if I console.log(file.name) immediately after getUploadParameters(file){ on the front end I successfully get the file name returned. I’m just not grabbing it the right way on the back end…

Note also that console.log(result.successful) in the front end successfully returns the file name. So the info is making the round trip. There’s just something than req.body.filename or req.filename or req.query.filename that can access it (and req.body returns an empty object). Pulling my hair out here…

In case there’s anyone out there (which I’m increasingly dubious of…):

After hours and hours and hours of struggling with the limited and confusing API documentation, some small progress: if, inside the fetch function on the client side, I change headers: {'content-type': 'application/json'} to headers: {'content-type': 'application/x-www-form-urlencoded' then on the server side, req.body is no longer an empty object, and instead returns { '{"filename":"Text3.txt","contentType":"text/plain"}': '' } - my filename finally appears!

Looks like an object inside an object - now I just need to figure out how to extract the Text3.txt part of that so I can use it in my AWS key…

Note that req.body.filename remains undefined.

I’ll continue to post any progress in the hopes someone else can avoid the massive frustrations I’ve been living through trying to get this to work…

A few grey hairs, some new wrinkles, select curse words, and much gnashing of teeth later, here’s my hacky solution:

To get the file name, I grabbed the first key from the stringified object noted above ({ '{"filename":"Text3.txt","contentType":"text/plain"}': '' }) - of which there’s only one anyway - and once I’ve grabbed that first one, I JSON.parse() it, and then grab the filename.

JSON.parse(Object.keys(req.body)[0]).filename

Now I can add a timestamp to it (to avoid collisions) and make that my key for AWS:

Key: `${Date.now().toString()}-${JSON.parse(Object.keys(req.body)[0]).filename}`,

That all goes on the Nodejs server side, such that my server code looks like this:

router.post("/uppytest", (req, res) => {
    const params = {
        Bucket: myBucket,
        Key: `${Date.now().toString()}-${JSON.parse(Object.keys(req.body)[0]).filename}`,
        ContentType: req.body.contentType,
    };
    s3.getSignedUrl('putObject', params, (err, url) => {
        res.status(200).json({
            method: 'put',
            url,
            fields: {},
        });
    });
});

For the sake of completeness for those thousands of your waiting with baited breath to see how this plays out, here’s the client side:

 const AwsS3 = Uppy.AwsS3
                              var uppy = Uppy.Core()
                                .use(Uppy.Dashboard, {
                                  trigger: ".UppyModalOpenerBtn",
                                  inline: false,
                                  closeModalOnClickOutside: true,
                                  target: '#drag-drop-area',
                                  replaceTargetContent: true,
                                  showProgressDetails: true,
                                  proudlyDisplayPoweredByUppy: false,
                                  note: "Images, Word, Excel, PDF, and similar files only, max 20 files of 30 MB each",
                                  height: 470,
                                  restrictions: {
                                	maxFileSize: 31457280, //i.e. 30MB
                            	      maxNumberOfFiles: 20,
                                	minNumberOfFiles: null,
                                	allowedFileTypes: null 
                            	  },
                                  metaFields: [
                            		{ id: 'name', name: 'Name', placeholder: 'You can rename the file here'},
                            		{id: 'caption', name: "Caption", placeholder: "Briefly describe what the file contains"}
                            	  ],
                                  browserBackButtonClose: true
                                })                                   
                                .use(AwsS3, {
                                    getUploadParameters(file){
                                         return fetch('/uppytest', {
                                            method: 'POST',
                                            headers: {
                                                'content-type': 'application/x-www-form-urlencoded'
                                            },
                                            body: JSON.stringify({
                                                filename: file.name,
                                                contentType: file.type
                                            })
                                        }).then((response) => {
                                            return response.json();
                                        }).then((data) => {
                                            return {
                                                method: data.method,
                                                url: data.url,
                                                fields: data.fields,
                                            };
                                        });
                                    },
                                })

The general principle, as I understand it, is that the AWS S3 plugin on the front end (i.e. the client browser) makes a request (via fetch() in my case) to the server (Nodejs in my case) for credentials and instructions on where the client should upload to - the client does the upload directly to AWS S3, but needs instructions from the server regarding where and how to do this, plus permission. Your server uses the AWS SDK to generate a unique, pre-signed URL which contains this information, and then passes that information back to the front end/client browser. The browser then uses that information to initiate the actual upload to the appropriate AWS S3 bucket, such that the file(s) never hit(s) your server.

There remains more work to be done - sorting out the CORS issue, adding time constraints to the pre-signed URL, some validation, etc. and the metaFields still don’t seem to do anything, but for now, my files are uploading to my AWS S3 bucket as intended, and named the way I want them named. That only took a week or so of utter frustration - Uppy is a case lesson in how not to document your API…

I hope that’s helpful to those of you who, like me, think Uppy is very, very cool, but find its documentation the exact opposite of cool. Happy to help troubleshoot any issues you encounter, as I’m able.

Cheers.

I’ve provided a detailed AWS S3 + NodeJS + Express + metadata + tags example here.