Uppy: Uploading to AWS S3, returning upload parameters with Node.js

Created on 10 May 2018  路  4Comments  路  Source: transloadit/uppy

Hi,

I would need some help with Uppy.

I have created custom URL called /api/invitation/upload, my code using Node and Express:

const envVars = require('../../../config/env-vars');
const express = require('express');

const aws = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');

const bucket = envVars.AWS_S3_BUCKET;

const router = express.Router();

aws.config.update({
  accessKeyId: envVars.AWS_ACCESS_KEY_ID,
  secretAccessKey: envVars.AWS_SECRET_ACCESS_KEY,
  signatureVersion: 'v4',
  region: 'eu-west-2',
});

const s3 = new aws.S3();

// Middleware multer for handling multipart/form-data and multer-s3 
// to save directly to AWS instead of locally

const upload = multer({
  storage: multerS3({
    s3,
    bucket,
    // Set public read permissions
    acl: 'public-read',
    // Auto detect content type
    contentType: multerS3.AUTO_CONTENT_TYPE,
    // Set key/filename as original uploaded name
    key: (req, file, cb) => {
      cb(null, `${Date.now().toString()}-${file.originalname}`);
    },
  }),
});

router.post('/api/invitation/upload', upload.single('image'), (req, res, next) => {
  res.status(200).json({
    method: 'post',
    url: req.file.location,
    fields: [],
  });
});


module.exports = router;

By using Paw I am posting file to URL mentioned with folowing

Content-Type: multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__
Content-Disposition: form-data; name="image"; filename="some-file-name.png"
Content-Type: image/png

The file gets successfully uploaded to my bucket.

Now I am trying to post with Uppy with following config:

  getUploadParameters(file) {
    // Send a request to our signing endpoint
    return fetch('/api/invitation/upload', {
      method: 'post',
      // Send and receive JSON.
      headers: {
        accept: 'application/json',
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        filename: file.name,
        contentType: file.type,
      }),
    }).then((response) => {
      // Parse the JSON response.
      return response.json();
    }).then((data) => {
      console.log('>>>', data);

      // Return an object in the correct shape.
      return {
        method: data.method,
        url: data.url,
        fields: data.fields,
      };
    });
  },
}

But getting server error, there is a req.file undefined. What am I missing here or doing wrong? Thank you for the help in advance.

AWS S3 Question

Most helpful comment

Hi, thank you for the help, it works!

I will post code, if somebody needs it in the future. Can you change the issue title, that it is clear that it involves Node? Thanks.

const envVars = require('../../../../config/env-vars');
const express = require('express');
const aws = require('aws-sdk');

const bucket = envVars.AWS_S3_BUCKET;
const router = express.Router();

aws.config.update({
  accessKeyId: envVars.AWS_ACCESS_KEY_ID,
  secretAccessKey: envVars.AWS_SECRET_ACCESS_KEY,
  signatureVersion: 'v4',
  region: 'eu-west-2',
});

const s3 = new aws.S3();

router.post('/api/invitation/upload', (req, res) => {
  const params = {
    Bucket: bucket,
    Key: `${Date.now().toString()}-${req.body.filename}`,
    ContentType: req.body.contentType,
  };

  s3.getSignedUrl('putObject', params, (err, url) => {
    res.status(200).json({
      method: 'put',
      url,
      fields: {},
    });
  });
});

module.exports = router;

All 4 comments

It looks like you are using the AwsS3 upload plugin. This plugin handles uploading in the browser; it does not upload to your own server, but to S3 directly. multer-s3 handles uploading on the server, it expects that you upload the file to your own server and then multer uploads it to S3.

You can either use multer-s3 with the XHRUpload plugin, or use the AwsS3 plugin, but not multer-s3 with the AwsS3 plugin.

When using the AwsS3 plugin, you are expected to return upload parameters, that means a request method, URL, form fields and headers that can be used to do the upload from the browser. uppy-server can create these upload parameters, or you can use a custom endpoint. This example uses a custom PHP endpoint, but it's quite similar in Node:

var url = s3.getSignedUrl('putObject', { Bucket, Key, ContentType, Body: '' })
return {
  url,
  method: 'PUT',
  fields: {},
}

Hope that makes sense!

Hi, thank you for the help, it works!

I will post code, if somebody needs it in the future. Can you change the issue title, that it is clear that it involves Node? Thanks.

const envVars = require('../../../../config/env-vars');
const express = require('express');
const aws = require('aws-sdk');

const bucket = envVars.AWS_S3_BUCKET;
const router = express.Router();

aws.config.update({
  accessKeyId: envVars.AWS_ACCESS_KEY_ID,
  secretAccessKey: envVars.AWS_SECRET_ACCESS_KEY,
  signatureVersion: 'v4',
  region: 'eu-west-2',
});

const s3 = new aws.S3();

router.post('/api/invitation/upload', (req, res) => {
  const params = {
    Bucket: bucket,
    Key: `${Date.now().toString()}-${req.body.filename}`,
    ContentType: req.body.contentType,
  };

  s3.getSignedUrl('putObject', params, (err, url) => {
    res.status(200).json({
      method: 'put',
      url,
      fields: {},
    });
  });
});

module.exports = router;

@be-codified really appreciate you posting your code - hugely helpful!

Wonder if you could post your front end js? I'm using your getUploadParameters above with your backend subsequent post but I cannot access the filename for the AWS key: req.body.filename is undefined.

To answer my own question, my solution was to change the header in the client side Uppy config code from:

headers: {
      'content-type': 'application/json',
},

to:

headers: {
       'content-type': 'application/x-www-form-urlencoded'
},

Then on the Nodejs server side, it returns:

{"filename":"file name.txt","contentType":"text/plain"}': '' })

So I used this to extract the file name:

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

Which I incorporated into the code which produces the key (i.e. file name) in the AWS S3 bucket:

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

More detailed write up on the Uppy support site here.

Was this page helpful?
0 / 5 - 0 ratings