Royce Birnbaum
Royce Birnbaum

Reputation: 185

axios upload to node.js API results in files in aws s3 becoming unreadable

Screen of Upload
Screen of Download
Whenever I utilize the client side upload panel the files that get uploaded to the bucket are named correctly but wont open. They are also a slightly different size from the front end uploaded version. I'm wondering if something in how i'm handling buffers / reading the files is off but I just started working with them yesterday so i'm fairly lost. If any of you based genius programmers could provide some insight into this issue I'd be eternally grateful!! The overall goal is the ability to upload files to an aws s3 bucket of any file type, then be able to download these files without them being modified or rendered unopenable.

server side javascript;

var express = require("express");
var mongodb = require("mongodb");
var _ = require("lodash");
var bodyParser = require("body-parser");
var app = express();
var router = express.Router();
var mongoose = require("mongoose");
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-1'});
var s3 = new AWS.S3({apiVersion: '2006-03-01'});

...
...
...

router.post('/upload', function (req, res) {
  var file = new File({name: req.body.fileName, type: req.body.contentType, buffer: req.body.file});
  var fs = require('fs');
  var fileStream = fs.createWriteStream(req.body.fileName);
  fileStream.write(req.body.file)
  fileStream.end(function () { console.log('done'); });
  fileStream.on('error', function(err) {
    console.log('File Error', err);
  });
  fs.readFile(req.body.fileName, (err, data) => {
     if (err) throw err;
     console.log(data);
     const params = {
         Bucket: req.body.crystalName,
         Key: req.body.fileName,
         Body: data
     };
     s3.upload(params, function(s3Err, data) {
         if (s3Err) {
           console.log(s3Err);
         } else {
           res.send(`File uploaded successfully at ${data.Location}`);
           console.log(`File uploaded successfully at ${data.Location}`);
         }
     });
  });
});

client side upload function (vue.js);

<template>
  <div class="main">
    <input v-model="crystalName" placeholder="Crystal Name" />
    <input type="file" @change="onFileChange"/>
    <button v-on:click="uploadToCrystal">Upload</button>
    <span>{{this.file}}</span>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'crystalviewer',
  props: ['user'],
  data: function () {
    return {
      crystalName: '',
      fileName: '',
      contentType: '',
      file: ''
    }
  },
  methods: {
    onFileChange (file) {
      let vue = this
      var reader = new FileReader()
      this.fileName = file.target.files[0].name || file.dataTransfer.files[0].name
      this.contentType = file.target.files[0].type || file.dataTransfer.files[0].type
      console.log(this.contentType)
      var start = 0
      var stop = file.target.files[0].size
      var blob = file.target.files[0].slice(start, stop)
      reader.onloadend = function (file) {
        vue.file = file.target.result
      }
      reader.readAsText(blob, 'utf-8')
    },
    uploadToCrystal () {
      let vue = this
      axios.post('https://api.mystic-crm.com/crystalviewer/upload', {
        crystalName: vue.crystalName,
        fileName: vue.fileName,
        contentType: vue.contentType,
        file: vue.file
      })
        .then(response => {
          console.log(response)
        })
        .catch(err => {
          console.log(err)
        })
    }
  }
}
</script>

<style scoped lang="less">
  .main {

  }
</style>

To get files after an upload run a get against;

https://api.mystic-crm.com/crystalviewer/contents/:crystalName/:fileName

where;

:crystalName = testcrystalmystic
:fileName = your_file_to_get

Upvotes: 1

Views: 2062

Answers (2)

to240
to240

Reputation: 381

React Hooks Version for anyone searching for this

Front End:

import React, {useState} from 'react'
import axios from 'axios'

function Amazonuploadtest() {
    const [file, setFile] = useState('')

const submitFile = (e) => {
    e.preventDefault();
    console.log(file[0])
    const formData = new FormData();
    formData.append('file', file);
    axios.post(`http://localhost:4000/upload`, formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    }).then(response => {
      console.log(response)
    }).catch(error => {
      console.log(error)
    });
  }


return (
    <div>
  <form onSubmit={submitFile} enctype="multipart/form-data" style={{paddingTop: 200}}>
    <input label='upload file' type='file' name="upl" onChange={e => setFile(e.target.files[0])} />
    <button type='submit'>Send</button>
  </form>
  </div>
);
}

export default Amazonuploadtest

Back End:

require('dotenv').config({ path: __dirname + '/.env' })
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const path = require('path')
const AWS = require('aws-sdk');
const fs = require('fs');
const multer = require('multer');
const multerS3 = require('multer-s3');


app.use(cors());
app.use(bodyParser.json());
app.use(cookieParser());

// #######################################    ALL AWS STUFF GOES HERE ###############################################################################################
AWS.config.update({
    secretAccessKey: 'YOUR KEY GOES HERE',
    accessKeyId: 'YOUR KEY GOES HERE',
    region: 'YOUR REGION GOES HERE -> You can find it in your bucket URL',
});

const s3 = new AWS.S3();

var upload = multer({
    storage: multerS3({
        s3: s3,
        bucket: 'YOUR BUCKET NAME -> Find it in your Bucket URL',
        key: function (req, file, cb) {
            cb(null, Date.now().toString() + `.png`); // gives every image upload a unique URL
        }
    })
});

app.post('/upload', upload.array('file',1), function (req, res, next) {
    const key = req.files[0].key
    console.log(key)
    res.send("Uploaded!");

   // We return the key here so that we can then save it as a URL in MongoDB -> allows 
   // you to call the image URL when rending the image on the front end.

   // example ->  let avatarURL = `https://s3.amazonaws.com/YOURBUCKETNAME/${key}`

});

Upvotes: 0

Royce Birnbaum
Royce Birnbaum

Reputation: 185

Turns out switching to Multiparty was the solution, now I can upload via forms. Apparently axios doesn't support file uploads but forms do so that was a fun revelation. edit added working front end

var express = require("express");
var AWS = require('aws-sdk');
AWS.config.update({region: 'us-west-1'});
var s3 = new AWS.S3({apiVersion: '2006-03-01'});
var multiparty = require('multiparty');

router.post('/upload', function (req, res) {
  var form = new multiparty.Form();
    var destPath;
    var crystalName;
    form.on('field', function(name, value) {
      if (name === 'crystalName') {
        crystalName = value
      } else if (name === 'fileName') {
        destPath = value;
      }
    });
    form.on('part', function(part) {
      s3.putObject({
        Bucket: crystalName,
        Key: destPath,
        ACL: 'public-read',
        Body: part,
        ContentLength: part.byteCount
      }, function(err, data) {
        if (err) throw err;
        console.log("done", data);
        res.end("OK");
        console.log("https://s3.amazonaws.com/" + crystalName + '/' + destPath);
      });
    });
    form.parse(req);
});

front end ex;

<template>
  <div class="main">
    <form v-on:submit="submit">
      <input name="crystalName" placeholder="Crystal Name" />
      <input name="fileName" v-model="fileName" placeholder="File Name" v-show="false" />
      <input name="file" type="file" @change="onFileChange"/>
      <input type="submit" value="Upload File" />
    </form>
  </div>
</template>

<script>
export default {
  name: 'crystalviewer',
  props: ['user'],
  data: function () {
    return {
      fileName: '',
      modal: ''
    }
  },
  methods: {
    submit (event) {
      let vue = this
      var form = document.forms[0]
      var request = new XMLHttpRequest()
      request.open('POST', 'https://api.mystic-crm.com/crystalviewer/upload', true)
      request.setRequestHeader('accept', 'multipart/form-data')
      event.preventDefault()
      var formData = new FormData(form)
      request.send(formData)
      request.onreadystatechange = function () {
        if (request.readyState < 4) {
          vue.modal = 'loading'
        } else if (request.readyState === 4) {
          if (request.status === 200 || request.status < 300) {
            vue.modal = 'success'
            console.log('success')
          } else {
            vue.modal = 'failure'
            console.log('failure')
          }
        }
      }
    },
    onFileChange (file) {
      this.fileName = file.target.files[0].name || file.dataTransfer.files[0].name
    }
  }
}
</script>

<style scoped lang="less">
  .main {

  }
</style>

Upvotes: 1

Related Questions