Nodejs Amazon S3 File Upload - node.js

I am trying to create a form that uploads my local files to my S3 bucket, but I'm a little confused as to where parts of the uploading logic should exist. I wanted to keep my POST route clean and reference the AWS logic that is housed in a separate file, but I'm a little confused as to what value should be used for the Body property for the params level and how I should reference this module when setting the completed upload URL to my database property at the fileAttachment line. Can anyone point me in the right direction?
Here is my aws-s3.js file:
module.exports = function() {
var path = require('path');
var async = require('async');
var fs = require('fs');
var AWS = require('aws-sdk');
var config = require(path.resolve(__dirname, '..', '..','./config/config.js'));
AWS.config.region = config.region;
var s3 = new AWS.S3({params: {Bucket: config.awsBucket}});
var params = {Key: config.awsAccessKeyId, Body: req.body.fileAttachment};
s3.upload(params, function(err, data){
if (err) {
console.log("Error uploading data: ", err);
} else {
console.log("Successfully uploaded data to " + config.awsBucket + " /myKey");
}
});
return s3;
};
Here is my route:
appRoutes.route('/create/file')
.get(function(req, res){
models.DiscoverySource.findAll({
where: {
organizationId: req.user.organizationId
}, attributes: ['discoverySource']
}).then(function(discoverySource){
res.render('pages/app/file-create.hbs',{
discoverySource: discoverySource
});
});
})
.post(function(req, res){
models.File.create({
discovery: req.body.discovery,
discoverySource: req.body.discoverySource,
fileAttachment:
userId: req.user.user_id
}).then(function(){
res.redirect('/app');
});
});
Form:
<form action="/app/create/file" method="post">
<div class="form-header">
<label for="data-discovery-source">Discovery Source:</label>
<select name="discoverySource">
{{#each discoverySource}}
<option value="{{this.discoverySource}}">{{this.discoverySource}}</option>
{{/each}}
</select>
<label for="data-discovery">Discovery:</label>
<textarea id="discovery-text-field" name="discovery"></textarea>
<label for="report-link">File Attachment:</label>
<input type="file" name="fileAttachment">
</div>
<button type="submit" id="create-button">Create File</button>
</form>

You can try using the multer-s3 module.
It allows you to upload your file with storaage configured to AWS.
This code uses the aws-sdk module and more about it's configuration can be found here.
Here is my code example:
It uses the recommended amazon AWS SDK for JavaScript in Node.js with
And it also uses multer express middleware for uploading files.
var aws = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')
var app = express()
var s3 = new aws.S3({ {accessKeyId: 'akid', secretAccessKey: 'secret'})
//this can also be configured in config file and through code
var upload = multer({
storage: multerS3({
s3: s3,
bucket: 'some-bucket',
key: function (req, file, cb) {
cb(null, Date.now().toString())
}
})
})
//the upload.array -means that you can load multiple files(max 3) named photos maximum 3, the resulting AWS full path urls will be returned in req.files
app.post('/upload', upload.array('photos', 3), function(req, res, next) {
//reference results that can be stored in database for example in req.files
res.send('Successfully uploaded ' + req.files.length + ' files!')
})
this codealso uses the multer npm module. More about its configuration possibilities like: single, any upload.array, fields can be found here.

You can alternatively use Minio-js here is an example of presigned-postpolicy.js. I hope it helps.
var Minio = require('minio')
var s3Client = new Minio({
endPoint: 's3.amazonaws.com',
accessKey: 'YOUR-ACCESSKEYID',
secretKey: 'YOUR-SECRETACCESSKEY',
insecure: false // Default is false.
})
// Construct a new postPolicy.
var policy = s3Client.newPostPolicy()
// Set the object name my-objectname.
policy.setKey("my-objectname")
// Set the bucket to my-bucketname.
policy.setBucket("my-bucketname")
var expires = new Date
expires.setSeconds(24 * 60 * 60 * 10) //10 days
policy.setExpires(expires)
policy.setContentLengthRange(1024, 1024*1024) // Min upload length is 1KB Max upload size is 1MB
s3Client.presignedPostPolicy(policy, function(e, formData) {
if (e) return console.log(e)
var curl = []
curl.push('curl https://s3.amazonaws.com/my-bucketname')
for (var key in formData) {
if (formData.hasOwnProperty(key)) {
var value = formData[key]
curl.push(`-F ${key}=${value}`)
}
}
// Print curl command to upload files.
curl.push('-F file=#<FILE>')
console.log(curl.join(' '))
})
Disclaimer: I work for Minio, Open Source object storage compatible with AWS S3.

Related

Multer's req.body works but req.files gives undefined

I'm trying to submit a form , that consists some text and an image file to the server. Regarding multer, my understanding is that, multer creates a storage folder for our images 'my-uploads/' and we pass on the key from formData.append('imageFile', imageFile) to upload.single('imageFile'). I tried giving paths like: my-uploads/, /my-uploads, ./my-uploads, so far none of it is working.
Next, using Fetch, I have been able to send the text body to the server and it reaches in [Object: null prototype].....(Not sure if it the right way of sending). The image files is not showing up the way expected too. Multer throws undefined when called req.files. Where is it going wrong with the code?
html:
<form class="blogForm" method="post" encType="multipart/form-data">
<input type="file" class="imageInput" name="file" multiple = "true"/>
<div class="blogEntryDiv" contenteditable="true"></div>
<input class= "blogSubmitButton" type="submit" value="Submit" >
</form>
js
document.querySelector('.blogForm').addEventListener('submit', (e) => {
let formData = new FormData();
let textContent = document.querySelector('.blogEntryDiv').innerText
let imageFile = document.querySelector('.imageInput').files
formData.append('textcontent', textContent);
formData.append('imageFile', imageFile);
fetch(`/someimage`, {
method: 'POST',
body: formData
}).then(function (res){
console.log(res);
}).then(json => console.log(json))
.catch(err => console.log(err));
})
app.js:
const multer = require('multer');
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'my-uploads/')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({ storage: storage })
app.post('/someimage', upload.single('imageFile'), (req, resp) => {
console.log(req.body)
console.log(req.files)//gives undefined
})
req.body gives:
[Object: null prototype] {
textcontent: '\n\nlorem lorem',
imageFile: '[object FileList]' //gives a string
}
formData.append('imageFile', imageFile) does not work, because imageFile is a file list, but you can only append single files. Use formData.append('imageFile', imageFile[0]).
Also, multer will write the single file into req.file, not into req.files.

How to upload images and keep track of them using a database using multer or busboy?

I am trying to upload images or pdfs (files in general) using express and the two modules i have come across are multer and busboy. The image uploading process is easy however i have come across problems with both of them. What i want is to upload the image with a name that I can later plug into a database too so that when i want to retrieve the file i can simply put the name of the file in the img tag and be done with it. In Busboy req.body for some reason is empty so I cannot pass anymore information using a form such as the username of the person. In multer we cannot customly name it we have to specify the filename inside the initial setup of multer. What i want is to have a form which enables us to enter a username and a file. Then i can using that username generate a random string which can be the same for both the file and the entry i will make into the database so that I can easily grab it later. Both the busboy and multer setup and usage is given below:
busboy setup:
router.post('/profileUpload', function(req,res){
let username = 'dual'
let busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
let saveTo = './public/profileimage/'+`${username}.jpg`;
console.log('Uploading: ' + saveTo);
file.pipe(fs.createWriteStream(saveTo));
});
busboy.on('finish', function() {
console.log('Upload complete');
});
return req.pipe(busboy);
})
Multer setup:
const multer = require('multer');
var storage = multer.diskStorage({
destination: './public/reports',
filename: function (req, file, cb){
cb(null,'report' + Date.now() + path.extname(file.originalname))
}
})
var upload = multer({ storage: storage })
router.post('/reportUpload',upload.single('doc'),function(req,res){
let usernameLab = req.session.usernameLab
let username = req.body.patientUsername
var d = new Date()
var uniqstring = Date.now()
let uniqname = 'report' + uniqstring
var date = `${d.getFullYear()}/${d.getMonth()+1}/${d.getDate()}`
let testName = req.body.testName
let department = req.body.department
let params = [username, usernameLab, uniqname, date, testName, department]
let promise = queries.reportUpload(params)
promise.then(function(){
res.redirect('/reportUpload?upload=success')
})
});

Google cloud function download file and redirect to bucket storage

I am trying to use a google cloud function in Node.js to download a file for wordpress repo then send the file into a google cloud bucket. I have the wordpress file downloading but it fails to write to the google bucket.
function writeToBucket(jsonObject){
/*
* Google API authentication
*/
var gcs = require('#google-cloud/storage')({
projectId: 'wp-media-cdn',
keyFilename: 'wp-media-cdn-d9d7c61bfad9.json'
});
/*
* rename image file with image size, format: size X size imgName
*/
var pluginUrl = "https://downloads.wordpress.org/plugin/bbpress.2.5.14.zip";
newPluginName = "bbpress";
/*
* Read image into stream, upload image to bucket
*/
var request = require('request');
var fs = require('fs'); //used for createWriteString()
var myBucket = gcs.bucket('test_buckyy'); //PUT BUCKET NAME HERE
var file = myBucket.file(nnewPluginName);
// file.exists() returns true if file already in bucket, then returns file url, exits function
if(file.exists()){
return 'https://storage.googleapis.com/${test_buckyy}/${file}';
}
//pipes image data into fileStream
var fileStream = myBucket.file(newImageName).createWriteStream();
request(imgUrl).pipe(fileStream)
.on('error', function(err) {
console.log('upload failed');
})
.on('finish', function() {
console.log('file uploaded');
});
/*
* return image url
* use getSignedUrl
*/
return 'https://storage.googleapis.com/${test_buckyy}/${file}';
}
I just replicated your use case scenario and I successfully downloaded the file into the temporary folder of a Cloud Function and from there I copied this file into a bucket.
In order to achieve this, I downloaded the file using createWriteStream into the /tmp folder since is the only folder where we can store files in a Cloud Function, as stated in the Cloud Functions Execution Environment documentation.
After that, I just copied the file to a bucket following this Cloud Storage Uploading Objects documentation.
You can take a look of my sample function
Index.js
const {Storage} = require('#google-cloud/storage');
exports.writeToBucket = (req, res) => {
const http = require('http');
const fs = require('fs');
const file = fs.createWriteStream("/tmp/yourfile.jpg");
const request = http.get("YOUR_URL_TO_DOWNLOAD_A_FILE", function(response) {
response.pipe(file);
});
console.log('file downloaded');
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
const bucketName = 'YOUR_BUCKET_NAME';
const filename = '/tmp/yourfile.jpg';
// Uploads a local file to the bucket
storage.bucket(bucketName).upload(filename, {
gzip: true,
metadata: {
cacheControl: 'no-cache',
},
});
res.status(200).send(`${filename} uploaded to ${bucketName}.`);
};
package.json
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"#google-cloud/storage": "^3.0.3"
}
}
Using Chris32's answer I've created a similar version but avoiding the download of the image to the tmp folder. Hope it's useful !
'use strict';
const http = require('http');
const {Storage} = require('#google-cloud/storage');
exports.http = (request, response) => {
const imageUrl = request.body.url;
const fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1);
const storage = new Storage({keyFilename: "keyfile.json"});
const bucket = storage.bucket('MY_BUCKET_NAME');
const file = bucket.file(fileName);
console.log('Uploading image')
http.get(imageUrl, function(res) {
res.pipe(
file.createWriteStream({
resumable: false,
public: true,
metadata: {
contentType: res.headers["content-type"]
}
})
);
});
console.log('Image uploaded')
response.status(201).send('Image successful uploaded!');
};
exports.event = (event, callback) => {
callback();
};

Using Node, Express and Multer for Upload a file to MongoDB? What should I use, Multer or GRIDfs?

Hello I'm running a final project on heroku using MongoLab and Multer to upload photos seems to work fine but after a few hours or less the photos dissapear from the page but leaves the img placeholder and another field like name or date area where it was.
Is this Heroku or Mongolab issue or is it just the free account is this because of the free acounts. also it doesnt matter if there is one photo uploaded or a dozen they will still dissapear after a while.
Could you help me? Is there a hack, tricky line of code or something that I made wrong with Multer or my database? I have made a deep research in the web and Stack-Overflow I found something call GRIDfs but I don't know if that will work. Could you tell me if I'm in rigth way?
This is my code for Multer:
var express = require("express"),
body_parser = require("body-parser"),
multer = require("multer"),
mongoose = require("mongoose");
var app = express();
//connect my local database to MongoLab database
var url = process.env.DATABASE || "mongodb://localhost/black_hat";
mongoose.connect(url);
Multer configuration
var storage = multer.diskStorage({
destination: function(req, file, callback){
callback(null, "./public/uploads");
},
filename: function(req, file, callback){
callback(null, Date.now() + file.originalname);
}
});
var upload = multer({storage : storage}).single("image");
My POST route
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
if(err){
return res.end("Error uploading file");
}
// get data from form
var newName = req.body.name,
newCover = "/uploads/" + req.file.filename;
// Packing into an object
var newCollage = {
name : newName,
cover: newCover
};
//create new album
Collage.create(newCollage, function(err, collage) {
if(err){
console.log(err);
} else {
// console.log(collage);
res.redirect("/fotos_videos");
}
});
});
});
The form where I upload the picture
<div style="width=30%; margin: 30px auto" >
<form action="/fotos_videos" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label>Album Name</label>
<input type="text" name="name" placeholder="Album Name" class="form-control">
</div>
<div class="form-group">
<label>Choose Album Cover</label>
<input type="file" name="image" placeholder="image url" class="form-control" required>
</div>
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block">Upload</button>
</div>
</form>
Back
</div>
Finally my Model for Collage
var mongoose = require("mongoose");
// Schema
var collageSchema = new mongoose.Schema({
name : String,
cover : String,
photos: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Photo"
}]
});
var Collage = mongoose.model("Collage", collageSchema);
module.exports = Collage;
Any kind of help will be appreciated.
if you want to save the image to the db u should not have "dest" property. also implementation of multer is wrong maybe because it is an old question and previous api was like that. here what u should u do.
configure your multer:
const multer = require("multer");
const upload = multer({
limits: { fileSize: 5000000 },
fileFilter(req, file, cb) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { //allowed file extensions
return cb(new Error("please upload png,jpeg or jpg"));
}
cb(undefined, true);
}
});
then create an endpoint. u already have this
app.post("/fotos_videos", isLoggedIn, function(req, res) {
upload(req, res, function(err) {
this iplementation wrong. upload is a middleware and if you wanna add authentication, only authenticated user should be access to this endpoint. so we need to tweak this endpoint.
Also u need to define a schema for uploads in order to save them to the db. let's say u created a model named uploads and this to it. we do not need to validate it by mongoose because multer already takes care of it. make sure u delete the dest property from multer configuration. because if u dont delete, multer will save the file to the folder so our async (req,res){} function will not be able to access to the file so we cannot save it to the db.
uploads:{type:Buffer} //include this in your uploads schema
then in this file call Uploads model.
router.post(
"/fotos_videos/me/uploads", isLoggedIn,
upload.single("uploads"), //uploads is just a random name when u upload from postman, form form-data,put "uploads" in key field
async (req, res) => {
req.upload.uploads = req.file.buffer;//i explain down below
await req.upload.save();
res.send();
},
(err, req, res) => {
res.status(400).send("error happned");
}
);
We do not store our pictures in the filesystem. The reason is almost all the deployment platforms, require us to take our code and push it up to the repository on their server. So the file system gets wiped every time we deploy which means we will lose data when we deploy. So instead of storing the images on the file system, we are going to add a field to the user model to store the image of binary data in the database.
now it is time to fetch our image from the db. for this u need to have a new route
router.get("/fotos_videos/:id/uploads", async (req, res) => {
try {
const upload = await Uploads.findById(req.params.id);
if (!upload || !upload.uploads) {
throw new Error("no image found");
}
res.set("Content-Type", "image/png");
res.send(upload.uploads);
} catch (e) {
res.status(400).send(e.message);
}
});
I don't know if this will be helpful. but from my understanding multer is the stuff used to get the data from the user (like uploaded images). It doesn't do persistent storage. If you want to store the data (large files like images) you should use gridfs-stream .
You should a create a read stream to GridFs once the data comes in from the user.

MEAN Stack File uploads

I've recently started programming with the MEAN Stack, and I'm currently implementing some sort of social network. Been using the MEAN.io framework to do so.
My main problem right now is getting the file upload to work, because what I want to do is receive the file from the form into the AngularJS Controller and pass it along with more info's to ExpressJS so I can finally send everything to MongoDB. (I'm building a register new user form).
I dont want to store the file itself on the database but I want to store a link to it.
I've searched dozens of pages on google with different search queries but I couldn't find anything that I could understand or worked. Been searching for hours to no result. That's why I've came here.
Can anyone help me with this?
Thanks :)
EDIT: Maybe a bit of the code would help understand.
The default MEAN.io Users Angular controller which I'm using as foundation has this:
$scope.register = function(){
$scope.usernameError = null;
$scope.registerError = null;
$http.post('/register', {
email: $scope.user.email,
password: $scope.user.password,
confirmPassword: $scope.user.confirmPassword,
username: $scope.user.username,
name: $scope.user.fullname
})//... has a bit more code but I cut it because the post is the main thing here.
};
What I want to do is:
Receive a file from a form, onto this controller and pass it along with email, password, name, etc, etc and be able to use the json on expressjs, which sits on the server side.
The '/register' is a nodejs route so a server controller which creates the user (with the user schema) and sends it to the MongoDB.
I recently did something just like this. I used angular-file-upload. You'll also want node-multiparty for your endpoint to parse the form data. Then you could use s3 for uploading the file to s3.
Here's some of my [edited] code.
Angular Template
<button>
Upload <input type="file" ng-file-select="onFileSelect($files)">
</button>
Angular Controller
$scope.onFileSelect = function(image) {
$scope.uploadInProgress = true;
$scope.uploadProgress = 0;
if (angular.isArray(image)) {
image = image[0];
}
$scope.upload = $upload.upload({
url: '/api/v1/upload/image',
method: 'POST',
data: {
type: 'profile'
},
file: image
}).progress(function(event) {
$scope.uploadProgress = Math.floor(event.loaded / event.total);
$scope.$apply();
}).success(function(data, status, headers, config) {
AlertService.success('Photo uploaded!');
}).error(function(err) {
$scope.uploadInProgress = false;
AlertService.error('Error uploading file: ' + err.message || err);
});
};
Route
var uuid = require('uuid'); // https://github.com/defunctzombie/node-uuid
var multiparty = require('multiparty'); // https://github.com/andrewrk/node-multiparty
var s3 = require('s3'); // https://github.com/andrewrk/node-s3-client
var s3Client = s3.createClient({
key: '<your_key>',
secret: '<your_secret>',
bucket: '<your_bucket>'
});
module.exports = function(app) {
app.post('/api/v1/upload/image', function(req, res) {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
var file = files.file[0];
var contentType = file.headers['content-type'];
var extension = file.path.substring(file.path.lastIndexOf('.'));
var destPath = '/' + user.id + '/profile' + '/' + uuid.v4() + extension;
var headers = {
'x-amz-acl': 'public-read',
'Content-Length': file.size,
'Content-Type': contentType
};
var uploader = s3Client.upload(file.path, destPath, headers);
uploader.on('error', function(err) {
//TODO handle this
});
uploader.on('end', function(url) {
//TODO do something with the url
console.log('file opened:', url);
});
});
});
}
I changed this from my code, so it may not work out of the box, but hopefully it's helpful!
Recently a new package was added to the list of packages on mean.io. It's a beauty!
Simply run:
$ mean install mean-upload
This installs the package into the node folder but you have access to the directives in your packages.
http://mean.io/#!/packages/53ccd40e56eac633a3eee335
On your form view, add something like this:
<div class="form-group">
<label class="control-label">Images</label>
<mean-upload file-dest="'/packages/photos/'" upload-file-callback="uploadFileArticleCallback(file)"></mean-upload>
<br>
<ul class="list-group">
<li ng-repeat="image in article.images" class="list-group-item">
{{image.name}}
<span class="glyphicon glyphicon-remove-circle pull-right" ng-click="deletePhoto(image)"></span>
</li>
</ul>
</div>
And in your controller:
$scope.uploadFileArticleCallback = function(file) {
if (file.type.indexOf('image') !== -1){
$scope.article.images.push({
'size': file.size,
'type': file.type,
'name': file.name,
'src': file.src
});
}
else{
$scope.article.files.push({
'size': file.size,
'type': file.type,
'name': file.name,
'src': file.src
});
}
};
$scope.deletePhoto = function(photo) {
var index = $scope.article.images.indexOf(photo);
$scope.article.images.splice(index, 1);
}
Enjoy!
Mean-upload has been obsoleted and is now called "upload". It is managed in - https://git.mean.io/orit/upload
I know this post is old. I came across it and #kentcdodds had an answer that i really liked, but the libraries he used are now out of date and I could not get them to work. So after some research i have a newer similar solution I want to share.
HTML using ng-upload
<form >
<div style="margin-bottom: 15px;">
<button type="file" name="file" id="file" ngf-select="uploadFiles($file, $invalidFiles)" accept="image/*" ngf-max-height="1000" ngf-max-size="1MB">Select File</button>
</div>
</form>
INCLUDE ng-upload module
download it, references the files and include the module
var app = angular.module('app', ['ngFileUpload']);
this will give you access to the Upload service.
Controller code
$scope.uploadFiles = function(file, errFiles) {
$scope.f = file;
$scope.errFile = errFiles && errFiles[0];
if (file) {
file.upload = Upload.upload({
url: 'you-api-endpoint',
data: {file: file}
});
//put promise and event watchers here if wanted
}
};
NODE api code
All the code below is in a separate route file which is required in my main server.js file.
require('./app/app-routes.js')(app, _);
var fs = require('fs');
var uuid = require('uuid');
var s3 = require('s3fs'); // https://github.com/RiptideElements/s3fs
var s3Impl = new s3('bucketname', {
accessKeyId: '<your access key id>',
secretAccessKey: '< your secret access key >'
});
var multiparty = require('connect-multiparty');
var multipartyMiddleware = multiparty();
module.exports = function(app, _) {
app.use(multipartyMiddleware);
app.post('/your-api-endpoint',function(req, res){
var file = req.files.file; // multiparty is what allows the file to to be accessed in the req
var stream = fs.createReadStream(file.path);
var extension = file.path.substring(file.path.lastIndexOf('.'));
var destPath = '/' + req.user._id + '/avatar/' + uuid.v4() + extension;
var base = 'https://you-bucket-url';
return s3Impl.writeFile(destPath, stream, {ContentType: file.type}).then(function(one){
fs.unlink(file.path);
res.send(base + destPath);
});
});
All i was trying to do was upload a unique avatar for a user. Hope this helps!!

Resources