I'm doing a file upload from angular implementing the https://github.com/nervgh/angular-file-upload plugin, my setup it's like this :
var vm = this,
apiUrl = appConfig.getItem('BASE_API_URL'), // base url;
vm.uploader = $scope.uploader = new FileUploader({
url: apiUrl + '/invoices/upload',
headers: {
"Content-Type": undefined
}
});
// this function is triggered by a button outside
vm.uploadAll = function () {
vm.uploader.uploadAll();
};
on the html I have
<input id="uploadFileButton"
type="file"
nv-file-select
uploader="FUCtrl.uploader"
multiple
style="display: none;"/>
// the display none is due to that this input is click triggered
// by an outside button
the thing is that for start on the client side the post request I see this
an image is uploaded (in theory), but the Content-Type is undefined, a missing enctype, and on the other hand on the server side I have this
var express = require('express'),
multer = require('multer'),
cors = require('cors');
var app = express();
app.use(cors());
app.get('*', function(){});
app.use(multer({dest:'./uploads/'}).single('photo'));
app.post('/upload', function(req, res){
console.log('hit');
console.log(req.body); // form fields
console.log(req.files); // form files
res.status(204).end();
});
app.listen(3000);
but when I recieve the post I see on the console
console.log(req.body); // {}
console.log(req.files); // undefined
and I can't get any data from the pdf's upload
what am I missing ?
hey man i don't know about that plugin. but i am using this https://github.com/danialfarid/ng-file-upload plugin and i find very helpful. this is the easiest way to upload a file.
Upload with form submit and validations:
http://jsfiddle.net/danialfarid/maqbzv15/1118/
Upload multiple files one by one on file select:
http://jsfiddle.net/danialfarid/2vq88rfs/136/
Upload multiple files in one request on file select (html5 only):
http://jsfiddle.net/danialfarid/huhjo9jm/5/
Upload single file on file select:
http://jsfiddle.net/danialfarid/0mz6ff9o/135/
Drop and upload with $watch:
http://jsfiddle.net/danialfarid/s8kc7wg0/400/
Image Crop and Upload
http://jsfiddle.net/danialfarid/xxo3sk41/590/
Finally I've discoverded the solution, on the HTML
nv-file-select
should be
nv-file-select=""
and remove
headers: {
"Content-Type": undefined
}
from the uploader configuration, apparently those things didn't set the content-type and his boundary well, so I toke them out and goy it to work
Related
EDIT:
The title has been changed so that anyone can suggest an alternative solution that achieves the same result with similar technology and platform. Not necessary has to be res.attachment.
I was trying to achieve a force download of PDF file by cross-origin URL. The code seems to work as expected but the downloaded file is ZERO BYTES, why?
On server:
app.get("/download", (req, res) => {
res.type("application/octet-stream");
// Note that the PDF URL is Cross-Origin.
res.attachment("https://cross-origin-domain-name.com/downloads/fiename.pdf");
res.send();
});
On HTML:
<a class="a-very-big-button" href="/download">Download PDF</a>
Do I miss anything? I did try out many other options like res.download() and readStream.pipe(res) methods, but most of them require the files to be on the same server. For my app, I need to help my clients to offer a download PDF button based on the URL submitted by them, which could be on their web server. Any advice would be appreciated! Thank you.
res.attachment does take a string as its only argument, but that string is used as a hint to the browser what the filename should be if the user decides to save the file. It does not allow you to specify a URL or filename to fetch.
Because you're not sending any data (res.send() without a Buffer or .write() calls), just a suggestion as to what the filename should be, the download is 0 bytes.
What you could do is pipe a HTTP request to res, which will have your server download and forward the file. The file will not be cached on your server and will 'cost' both upload and download capacity (but no storage).
An example on how to pipe a HTTPS request to a response.
Instead of Node's built-in https.request you could use many other libraries. Most of them support streaming files. These libraries can make it easier to handle errors.
const express = require('express');
const https = require('https');
const app = express();
const url = 'https://full-url-to-your/remote-file.pdf';
const headerAllowList = [
'content-type', 'content-length', 'last-modified', 'etag'
];
app.use('/', async (req, res, next) => {
// Create a HTTPS request
const externalRequest = https.request(url, {
headers: {
// You can add headers like authorization or user agent strings here.
// Accept: '*/*',
// 'User-Agent': '',
},
}, (externalResponse) => {
// This callback won't start until `.end()` is called.
// To make life easier on ourselves, we can copy some of the headers
// that the server sent your Node app and pass them along to the user.
headerAllowList
.filter(header => header in externalResponse.headers)
.forEach(header => res.set(header, externalResponse.headers[header]));
// If we didn't have content-type in the above headerAllowList,
// you could manually tell browser to expect a PDF file.
// res.set('Content-Type', 'application/pdf');
// Suggest a filename
res.attachment('some-file.pdf');
// Start piping the ReadableStream to Express' res.
externalResponse.pipe(res);
});
externalRequest.on('error', (err) => {
next(err);
});
// Start executing the HTTPS request
externalRequest.end();
});
app.listen(8000);
If you visit localhost:8000 you'll be served a PDF with a save-file dialog with the suggested filename, served from the specified URL.
I am creating a simple application as a learning experience with ionic 2 that connects to a node server running multer to upload images. The problem I am running into is, I need to get the final file name from multer back to the app that uploaded it, to save it locally. I am using the Auth and User modules from #ionic/cloud-angular for authentication and user information. The User object has a details object with things like "name", "email", "username", "image", ...
So, when an image is uploaded to the node server and multer saves it, I need to get that filename back to the application so I can update the user.details.image property.
Here is a simple version of my node server...
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var multer = require('multer');
var cors = require('cors');
var postStorage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, './uploads');
},
filename: function(req, file, callback) {
let fileName = '', postName;
if(typeof req.body.postName !== "undefined") {
postName = req.body.postName.toLowerCase().replace(/ /g, '-');
filename += postName;
}
fileName += new Date().getTime();
fileName += ".png";
callback(null, fileName);
}
});
var app = express();
app.set('port', process.env.PORT || 3000);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cors());
app.post('/upload', function(req, res) {
var uploadPost = multer({storage: postStorage}).single('post_image');
uploadPost(req, res, function(err) {
if(err) {
return res.end("error uploading file");
}
res.end("file uploaded");
});
});
app.listen(app.get('port');
console.log("app listening on port: " _ app.get('port'));
Is there a way to have it send back the final file name that multer saved? Something like res.json({fileName: fileName}); res.end();
I really don't know at this point, and all the tutorials I can find on multer just show how to create a new filename and save a file in disk or database or memory, but nowhere does it show how you can actually get that filename back to the application that uploaded it.
Thank you in advance for any help, I really appreciate it. If you need to see the ionic 2 code that uploads the stuff please let me know and I will update the post with that code as well. Thank you.
If you are uploading single file you can get filename using req.file.filename
if you upload multiple files req.files
Once you got file name , you can send it to client using res.send({filename:FileName});
I'm working with feathers.js on the back-end and React on the front end and I need to implement a way to upload a picture. I'm using multer to handle the upload (and I've tried using busboy as well), but I can't seem to get the actual picture uploaded, or at least access it on req.file.
On the client side I have:
<form method="post" enctype="multipart/form-data" action="/picture/upload">
<input type="file" name="avatar" />
<input type="submit" value="Submit" />
</form>
In /src/middleware/index.js I have:
'use strict';
const uploadPicture = require('./uploadPicture');
const handler = require('feathers-errors/handler');
const notFound = require('./not-found-handler');
const logger = require('./logger');
var multer = require('multer');
const upload = multer({ dest: '../../client/img'});
module.exports = function() {
// Add your custom middleware here. Remember, that
// just like Express the order matters, so error
// handling middleware should go last.
const app = this;
app.post('/picture/upload', upload.single('avatar'), uploadPicture(app));
app.use(notFound());
app.use(logger(app));
app.use(handler());
};
This is src/middleware/uploadPicture.js:
'use strict';
module.exports = function(app) {
return function(req, res, next) {
console.log('req.file', req.file);
console.log('req.body', req.body);
};
};
req.file is always undefined, and req.body does contain the name of the image I uploaded.
I have tried using mutler and busboy on a basic Express project for testing, and it works perfectly, so that makes me think that maybe it has something to do with the middleware feathers.js uses and probably it changes some header or something, so multer can't append the file to the request object.
This is the order in which middleware is defined in src/app.js, which is where the server instance is run:
app.use(compress())
.options('*', cors())
.use(cors())
.use('/', serveStatic( app.get('client') ))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(hooks())
.configure(rest())
.configure(services)
.configure(middleware);
Any thoughts on how to handle the image upload in this scenario?
I am using react, so enctype is not a supported HTML attribute. I should have used encType in my form. That fixed the problem.
I have gotten this far to accept a file in my HTML form and post in with angular via an $http.post using the ng-file-upload module. Now I want to accept this file in Mongoose and store it into my NoSQL MongoDB hosted on MongoLab.
I have read about this module called Multer and followed the basic documentation, but it only me as far. Before I explain the beginning of the problem let me post my Code:
My HTML form:
<form name="upForm">
<fieldset>
<legend>Upload files here</legend>
<label>Insert File Here:</label>
<input type="file" ngf-select ng-model="exFile" name="file" ngf-accept="'.cs'" required>
<i ng-show="upForm.file.$error.required">*required</i>
<div class="alert" ng-show="file.$error === 'pattern'">
file type is not accepted
</div>
<br />
<button ng-disabled="!upForm.$valid" ng-click="uploadExercise(exFile)" class="btn btn-default">Submit</button>
<span class="progress" ng-show="picFile.progress >= 0">
<div style="width:{{exFile.progress}}%" ng-bind="picFile.progress + '%'"></div>
</span>
<span ng-show="picFile.result">Upload Successful</span>
</fieldset>
</form>
My Angular Code:
$scope.uploadExercise = function (file) {
console.log(file);
var fd = new FormData();
fd.append('file', file);
$http.post(url+"/Upload", fd,{
transformRequest: angular.identity,
header:{'Content-Type': undefined},
enctype:'multipart/form-data'
}).success(function () { }).error(function () { });
console.log(fd);
};
console logs return the correct file objects.
Mongoose so far:
var mongoose = require("mongoose");
var express = require("express");
var multer = require('multer');
var upload = multer({ dest: 'Uploads/' });
var bodyparser = require("body-parser");
var app = express();
mongoose.connect("connection-string");
app.use(bodyparser.json());
app.post('/Upload', upload.single('solution') ,function (req, res, next) {
console.log(req.file);
});
This console.log keeps returning undefined. So something, somewhere went terribly wrong. Please help me out!
I want to receive this file in my Mongoose and store it into the MongoDB, I have never done this before and can't seem to find any decent documentation for Multer or any decent explanation for storing files that is relevant for my case. What am I doing wrong? What should I be doing instead?
Man you are running on the wrong path. I have already explained in your previous question request that Multer is used to save files in file disk system and not to your database directly. For that you must use GRIDFS.
Coming to your current question.
app.post('/Upload', upload.single('solution') ,function (req, res, next) {
console.log(req.file);
});
Here the upload.single('solution') - calls a function Upload and the file name passed is solution but it is obvious enough that it isn't available here.
use this type of format - documentation of Multer
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now())
}
})
var upload = multer({ storage: storage })
The Storage Part there is used to give path to where your file must be saved and the file name section is used to make changes to the file name that you would like to have.
Please read the documentation because that'll help. When we use third party modules we must acknowledge the information they have already given so that we can use their work easily.
Let me make it easier for you. Here is ready made code that works.
Multer throwing weird error while uploading file via ng-file upload
Go check that thread. The question was raised by me - the problem there was I was sending files in array format, as in multiple files at once. If you are not doing that just change ng-file-upload segment to use the single upload demo example and on server side nodejs code replace .array with .singleand things will work the way you want them to work - given that you want to use file disk system to store files.
I repeat that this method wont help you to save the file in mongodb directly.
Let me know if you need any further clarification.
After some research I found the answer to my problem. I'm able to store files into my MongoDB now. For those who are interested I'm posting my Mongoose code below with a small description.
Start by installing packages required for this operation. I used the following ones
npm install --save formidable
npm install --save gridfs
npm install --save fs
these are the packages I used ( Formidable, gridfs and fs).
this is the fully working mongoose code for me relevant to this particular form and request.
var mongoose = require("mongoose");
var express = require("express");
var formidable = require("formidable");
var fs = require("fs");
var grid = require("gridfs-stream");
var bodyparser = require("body-parser");
var app = express();
mongoose.connect("**fill in your connection-url here**");
var conn = mongoose.connection;
app.use(bodyparser.json());
app.post('/Upload', function (req, res) {
var form = new formidable.IncomingForm();
form.uploadDir = __dirname+"/Uploads";
form.keepExtensions = true;
form.parse(req, function (err, fields, files) {
if (!err) {
console.log('Files Uploaded: ' + files.file)
grid.mongo = mongoose.mongo;
var gfs = grid(conn.db);
var writestream = gfs.createWriteStream({
filename: files.file.name
});
fs.createReadStream(files.file.path).pipe(writestream);
}
});
form.on('end', function () {
res.send('Completed ... go check fs.files & fs.chunks in mongodb');
});
});
this worked for me! I now went to look in my mongoDB hosted on MongoLab and see that fs.chunks and fs.files collections were created and fs.files contains the correct data.
So for those who have this problem, this is a solution and the documentation on http://excellencenodejsblog.com/gridfs-using-mongoose-nodejs/ helped me out a lot. Now that I have this working, I also want to download the file FROM mongoDB onto a chosen directory on my pc, is there anyone who can give me the answer to that? or is that just as simple as to create a readstream to a file-system?
I can't figure out how to post through angular $http. Why? I'd like multer to parse and store my file and angular to get a copy when it is done. Better yet, I'd love to just let angular get a copy, then pass it down to the server.
I am able to upload a file using the snippets below:
// view (jade)
.container
form(action="/upload", method="POST", enctype="multipart/form-data")
input(type="file", name="f")
input(type="submit", value="Upload")
// post route (express)
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('f'), function(req, res) {
console.log(req.file);
});
Running this with any sample file, returns the json I want to the console and the image is stored successfully in the proper directory. However:
// view (jade)
form(enctype="multipart/form-data" ng-submit="uploadFile(file)")
input(type="file", name="f", ng-model="file")
input(type="submit", value="Upload")
// ctrl (angular)
$scope.uploadFile = function(file) {
console.log(file);
$http.post('/upload', file);
};
// post route (express)
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('f'), function(req, res) {
console.log(req.file);
});
This returns undefined on both console inputs, even though I didn't change the file, I only changed how it was sent.
This leads me two believe one of two things:
The data submitted is not what I think it is. If this is the case, what is it? Nothing prints on the log.
OR
The data submitted is altered in some way by angular. If this is the case, how? Again nothing prints on the log.
In the first case, by sending the data directly through the form to the server, you let html do the transformation magic under the hood.
In the second case, by posting through AngularJs via $http, you need to tell the middleware that is $http, the required transformation it needs to do to the request to match the attributes you passed to the form.
For having struggled with this myself for a while, here is an adaptation of the code I used (for $resource). But I think it should work for its underlying $http.
So in your controller:
$scope.uploadFile = function(file) {
console.log(file);
var fd = new FormData();
fd.append('file', file);
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {
'Content-Type': undefined},
enctype: 'multipart/form-data'
}
})
.success(function(){
})
.error(function(){
});
};
For that to work, you need to bind the file input field to the model. Of course, Angularjs chooses not to do that natively for some reasons.
In the jade view:
form(enctype="multipart/form-data" ng-submit....)
input(type="file", name="f", ng-model="file" on change='angular.element(this).scope().readFile(this)')
input(type="submit", value="Upload")
onchange is JavaScript and not AngularJs and it triggers the scope.readfile() function that we need to define:
In the controller:
$scope.readfile = function(elem) {
var file= elem.files[0];
var reader = new FileReader();
reader.onload = function() {
$scope.$apply(function(){
$scope.file = file;
$scope.imageUrl = reader.result // to display image via ng-Src
})
}
reader.readAsDataURL(file);
}
I think that should bring back some html magic back into angular when working with forms.
I suggest you look into JavaScript's onchange, FormData, and FileReader for code snippets like these and better documentation.